AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/15 16:25 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "KMSANReasoning": "",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series introduces support for the Rockchip RK3568 DesignWare Ethernet PCS (XPCS) and refactors PCS initialization in the STMMAC Ethernet driver family (dwmac-rk, dwmac-intel, dwmac-renesas, dwmac-socfpga, dwmac-rzn1). All modified code is structurally unreachable on amd64 in standard virtualized fuzzing environments (QEMU/GCE): documentation/device tree bindings and arm64 DTS changes are non-executable on amd64, and the affected drivers (stmmac dwmac variants and Synopsys DW XPCS) require physical SoC platform devices or physical Intel/Rockchip/Renesas silicon not emulated by QEMU.",
  "WorthFuzzing": false
}

1/1 2026/09/15 16:25 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 011fc215fabd5fa95179cc38bd0ba811e60f999c\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Sep 15 16:25:35 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml b/Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml\nnew file mode 100644\nindex 0000000000000..703fcff0e3f70\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml\n@@ -0,0 +1,110 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/net/pcs/rockchip,rk3568-xpcs.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: Rockchip RK3568 Synopsys DesignWare Ethernet PCS\n+\n+maintainers:\n+  - Coia Prant \u003ccoiaprant@gmail.com\u003e\n+\n+description: |\n+  Rockchip RK3568 SoC integrates a Synopsys DesignWare Ethernet Physical\n+  Coding Sublayer (XPCS).\n+  The PCS provides an interface between the Media Access Control (MAC)\n+  and the Physical Medium Attachment (PMA) sublayer through a Media\n+  Independent Interface (GMII).\n+\n+  The XPCS is accessed over the APB3 bus and internally connected to a\n+  Naneng Combo SerDes PHY.\n+  It supports 1000BASE-X, SGMII and QSGMII modes.\n+\n+  The block contains four MII ports that can be individually enabled and\n+  routed to one of the Ethernet GMAC controllers via the pcs-handle\n+  property in the MAC device tree node.\n+\n+properties:\n+  compatible:\n+    const: rockchip,rk3568-xpcs\n+\n+  reg:\n+    maxItems: 1\n+\n+  \"#address-cells\":\n+    const: 1\n+\n+  \"#size-cells\":\n+    const: 0\n+\n+  clocks:\n+    items:\n+      - description: APB3 bus interface clock (clk_csr_i), required for register access\n+      - description: EEE clock (clk_eee_i), required for Energy Efficient Ethernet operation\n+\n+  clock-names:\n+    items:\n+      - const: csr\n+      - const: eee\n+\n+  phys:\n+    maxItems: 1\n+\n+  phy-names:\n+    const: serdes\n+\n+  power-domains:\n+    maxItems: 1\n+\n+patternProperties:\n+  \"^ethernet-pcs-mii@[0-3]$\":\n+    type: object\n+    description:\n+      One of the four MII ports of the XPCS. The port is linked to an\n+      Ethernet MAC controller via the pcs-handle property in the MAC's\n+      device tree node.\n+\n+    properties:\n+      reg:\n+        description: MII port number.\n+        enum: [0, 1, 2, 3]\n+\n+    required:\n+      - reg\n+\n+    additionalProperties: false\n+\n+required:\n+  - compatible\n+  - reg\n+  - \"#address-cells\"\n+  - \"#size-cells\"\n+  - clocks\n+  - clock-names\n+  - phys\n+  - phy-names\n+  - power-domains\n+\n+additionalProperties: false\n+\n+examples:\n+  - |\n+    #include \u003cdt-bindings/clock/rk3568-cru.h\u003e\n+    #include \u003cdt-bindings/power/rk3568-power.h\u003e\n+    #include \u003cdt-bindings/phy/phy.h\u003e\n+\n+    ethernet-pcs@fda00000 {\n+      compatible = \"rockchip,rk3568-xpcs\";\n+      reg = \u003c0xfda00000 0x200000\u003e;\n+      #address-cells = \u003c1\u003e;\n+      #size-cells = \u003c0\u003e;\n+      clocks = \u003c\u0026cru PCLK_XPCS\u003e, \u003c\u0026cru CLK_XPCS_EEE\u003e;\n+      clock-names = \"csr\", \"eee\";\n+      phys = \u003c\u0026combphy2 PHY_TYPE_SGMII\u003e;\n+      phy-names = \"serdes\";\n+      power-domains = \u003c\u0026power RK3568_PD_PIPE\u003e;\n+\n+      ethernet-pcs-mii@0 {\n+        reg = \u003c0\u003e;\n+      };\n+    };\ndiff --git a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml\nindex 80c252845349c..67b21ea77ec96 100644\n--- a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml\n+++ b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml\n@@ -120,6 +120,12 @@ properties:\n     maximum: 0x7F\n     default: 0x10\n \n+  pcs-handle:\n+    description:\n+      Specifies a reference to a node representing the PCS device\n+      connected to this GMAC. Required when phy-mode is \"sgmii\".\n+    maxItems: 1\n+\n   phy-supply:\n     description: PHY regulator\n \n@@ -159,6 +165,15 @@ allOf:\n         clocks:\n           minItems: 5\n \n+  - if:\n+      properties:\n+        phy-mode:\n+          contains:\n+            const: sgmii\n+    then:\n+      required:\n+        - pcs-handle\n+\n unevaluatedProperties: false\n \n examples:\ndiff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml b/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml\nindex 379b08bd9e97a..8e898bce9af73 100644\n--- a/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml\n+++ b/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml\n@@ -80,6 +80,15 @@ properties:\n     description:\n       Some additional pipe settings are accessed through GRF regs.\n \n+  rockchip,sgmii-mac-sel:\n+    $ref: /schemas/types.yaml#/definitions/uint32\n+    enum: [0, 1]\n+    default: 1\n+    description:\n+      Select gmac0 or gmac1 to be used as SGMII controller.\n+      The hardware reset value is GMAC1 (1). Set this to 0 to route\n+      SGMII to GMAC0.\n+\n   \"#phy-cells\":\n     const: 1\n \n@@ -105,6 +114,10 @@ allOf:\n           maxItems: 1\n         reset-names:\n           maxItems: 1\n+        rockchip,sgmii-mac-sel: true\n+    else:\n+      properties:\n+        rockchip,sgmii-mac-sel: false\n   - if:\n       properties:\n         compatible:\ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex 3b2eb2a7a89a8..f53a519da6298 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -23739,6 +23739,15 @@ S:\tMaintained\n F:\tDocumentation/devicetree/bindings/sound/rockchip,rk3576-sai.yaml\n F:\tsound/soc/rockchip/rockchip_sai.*\n \n+ROCKCHIP XPCS DRIVER\n+M:\tCoia Prant \u003ccoiaprant@gmail.com\u003e\n+L:\tnetdev@vger.kernel.org\n+L:\tlinux-rockchip@lists.infradead.org\n+S:\tMaintained\n+F:\tDocumentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml\n+F:\tdrivers/net/pcs/pcs-xpcs-rk.c\n+F:\tinclude/linux/pcs/pcs-xpcs-rk.h\n+\n ROCKER DRIVER\n M:\tJiri Pirko \u003cjiri@resnulli.us\u003e\n L:\tnetdev@vger.kernel.org\ndiff --git a/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts b/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts\nindex 58c1052ba8ef3..fdaa4a2a4328b 100644\n--- a/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts\n+++ b/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts\n@@ -3,6 +3,7 @@\n /dts-v1/;\n \n #include \u003cdt-bindings/gpio/gpio.h\u003e\n+#include \u003cdt-bindings/leds/common.h\u003e\n #include \u003cdt-bindings/pinctrl/rockchip.h\u003e\n #include \u003cdt-bindings/soc/rockchip,vop2.h\u003e\n #include \"rk3568.dtsi\"\n@@ -241,6 +242,7 @@ \u0026combphy1 {\n };\n \n \u0026combphy2 {\n+\trockchip,sgmii-mac-sel = \u003c0\u003e;\n \tstatus = \"okay\";\n };\n \n@@ -260,9 +262,18 @@ \u0026cpu3 {\n \tcpu-supply = \u003c\u0026vdd_cpu\u003e;\n };\n \n-/* Motorcomm YT8521SC LAN port (require SGMII) */\n+/* Motorcomm YT8521SC LAN port */\n \u0026gmac0 {\n-\tstatus = \"disabled\";\n+\tassigned-clocks = \u003c\u0026cru SCLK_GMAC0_RX_TX\u003e;\n+\tassigned-clock-parents = \u003c\u0026clk_gmac0_xpcs_mii\u003e;\n+\tmanaged = \"in-band-status\";\n+\tpcs-handle = \u003c\u0026xpcs_mii0\u003e;\n+\tphy-handle = \u003c\u0026sgmii_phy\u003e;\n+\tphy-mode = \"sgmii\";\n+\tphy-supply = \u003c\u0026vcc_3v3\u003e;\n+\tpinctrl-names = \"default\";\n+\tpinctrl-0 = \u003c\u0026gmac0_miim\u003e;\n+\tstatus = \"okay\";\n };\n \n /* Motorcomm YT8521SC WAN port */\n@@ -341,6 +352,36 @@ \u0026i2s0_8ch {\n \tstatus = \"okay\";\n };\n \n+\u0026mdio0 {\n+\tsgmii_phy: ethernet-phy@3 {\n+\t\tcompatible = \"ethernet-phy-ieee802.3-c22\";\n+\t\treg = \u003c0x3\u003e;\n+\t\tmax-speed = \u003c1000\u003e;\n+\t\treset-assert-us = \u003c20000\u003e;\n+\t\treset-deassert-us = \u003c100000\u003e;\n+\t\treset-gpios = \u003c\u0026gpio3 RK_PC6 GPIO_ACTIVE_LOW\u003e;\n+\n+\t\tleds {\n+\t\t\t#address-cells = \u003c1\u003e;\n+\t\t\t#size-cells = \u003c0\u003e;\n+\n+\t\t\tled@1 {\n+\t\t\t\treg = \u003c1\u003e;\n+\t\t\t\tcolor = \u003cLED_COLOR_ID_AMBER\u003e;\n+\t\t\t\tfunction = LED_FUNCTION_LAN;\n+\t\t\t\tdefault-state = \"keep\";\n+\t\t\t};\n+\n+\t\t\tled@2 {\n+\t\t\t\treg = \u003c2\u003e;\n+\t\t\t\tcolor = \u003cLED_COLOR_ID_GREEN\u003e;\n+\t\t\t\tfunction = LED_FUNCTION_LAN;\n+\t\t\t\tdefault-state = \"keep\";\n+\t\t\t};\n+\t\t};\n+\t};\n+};\n+\n \u0026mdio1 {\n \trgmii_phy: ethernet-phy@3 {\n \t\tcompatible = \"ethernet-phy-ieee802.3-c22\";\n@@ -350,6 +391,25 @@ rgmii_phy: ethernet-phy@3 {\n \t\treset-gpios = \u003c\u0026gpio4 RK_PC0 GPIO_ACTIVE_LOW\u003e;\n \t\trx-internal-delay-ps = \u003c1500\u003e;\n \t\ttx-internal-delay-ps = \u003c1500\u003e;\n+\n+\t\tleds {\n+\t\t\t#address-cells = \u003c1\u003e;\n+\t\t\t#size-cells = \u003c0\u003e;\n+\n+\t\t\tled@1 {\n+\t\t\t\treg = \u003c1\u003e;\n+\t\t\t\tcolor = \u003cLED_COLOR_ID_AMBER\u003e;\n+\t\t\t\tfunction = LED_FUNCTION_WAN;\n+\t\t\t\tdefault-state = \"keep\";\n+\t\t\t};\n+\n+\t\t\tled@2 {\n+\t\t\t\treg = \u003c2\u003e;\n+\t\t\t\tcolor = \u003cLED_COLOR_ID_GREEN\u003e;\n+\t\t\t\tfunction = LED_FUNCTION_WAN;\n+\t\t\t\tdefault-state = \"keep\";\n+\t\t\t};\n+\t\t};\n \t};\n };\n \n@@ -586,3 +646,13 @@ \u0026xin32k {\n \tpinctrl-names = \"default\";\n \tpinctrl-0 = \u003c\u0026clk32k_out1\u003e;\n };\n+\n+\u0026xpcs {\n+\tphys = \u003c\u0026combphy2 PHY_TYPE_SGMII\u003e;\n+\tphy-names = \"serdes\";\n+\tstatus = \"okay\";\n+};\n+\n+\u0026xpcs_mii0 {\n+\tstatus = \"okay\";\n+};\ndiff --git a/arch/arm64/boot/dts/rockchip/rk3568.dtsi b/arch/arm64/boot/dts/rockchip/rk3568.dtsi\nindex 3bc653f027f1f..227d03e336043 100644\n--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi\n+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi\n@@ -110,6 +110,51 @@ sata0: sata@fc000000 {\n \t\tstatus = \"disabled\";\n \t};\n \n+\txpcs: ethernet-pcs@fda00000 {\n+\t\tcompatible = \"rockchip,rk3568-xpcs\";\n+\t\t#address-cells = \u003c1\u003e;\n+\t\t#size-cells = \u003c0\u003e;\n+\t\treg = \u003c0x0 0xfda00000 0x0 0x200000\u003e;\n+\t\tclocks = \u003c\u0026cru PCLK_XPCS\u003e, \u003c\u0026cru CLK_XPCS_EEE\u003e;\n+\t\tclock-names = \"csr\", \"eee\";\n+\t\tpower-domains = \u003c\u0026power RK3568_PD_PIPE\u003e;\n+\t\tstatus = \"disabled\";\n+\n+\t\txpcs_mii0: ethernet-pcs-mii@0 {\n+\t\t\treg = \u003c0\u003e;\n+\t\t\tstatus = \"disabled\";\n+\t\t};\n+\n+\t\txpcs_mii1: ethernet-pcs-mii@1 {\n+\t\t\treg = \u003c1\u003e;\n+\t\t\tstatus = \"disabled\";\n+\t\t};\n+\n+\t\txpcs_mii2: ethernet-pcs-mii@2 {\n+\t\t\treg = \u003c2\u003e;\n+\t\t\tstatus = \"disabled\";\n+\t\t};\n+\n+\t\txpcs_mii3: ethernet-pcs-mii@3 {\n+\t\t\treg = \u003c3\u003e;\n+\t\t\tstatus = \"disabled\";\n+\t\t};\n+\t};\n+\n+\tclk_gmac0_xpcs_mii: clock-xpcs-gmac0 {\n+\t\tcompatible = \"fixed-clock\";\n+\t\tclock-frequency = \u003c125000000\u003e;\n+\t\tclock-output-names = \"clk_gmac0_xpcs_mii\";\n+\t\t#clock-cells = \u003c0\u003e;\n+\t};\n+\n+\tclk_gmac1_xpcs_mii: clock-xpcs-gmac1 {\n+\t\tcompatible = \"fixed-clock\";\n+\t\tclock-frequency = \u003c125000000\u003e;\n+\t\tclock-output-names = \"clk_gmac1_xpcs_mii\";\n+\t\t#clock-cells = \u003c0\u003e;\n+\t};\n+\n \tpipe_phy_grf0: syscon@fdc70000 {\n \t\tcompatible = \"rockchip,rk3568-pipe-phy-grf\", \"syscon\";\n \t\treg = \u003c0x0 0xfdc70000 0x0 0x1000\u003e;\ndiff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig\nindex e3dd5adda5aca..5088acc06982e 100644\n--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig\n+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig\n@@ -170,6 +170,7 @@ config DWMAC_ROCKCHIP\n \tdefault ARCH_ROCKCHIP\n \tdepends on OF \u0026\u0026 (ARCH_ROCKCHIP || COMPILE_TEST)\n \tselect MFD_SYSCON\n+\tselect PCS_XPCS_ROCKCHIP\n \thelp\n \t  Support for Ethernet controller on Rockchip RK3288 SoC.\n \ndiff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c\nindex f5f9fa67ecd77..4308dccbf2570 100644\n--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c\n+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c\n@@ -603,13 +603,47 @@ static void common_default_data(struct plat_stmmacenet_data *plat)\n \tplat-\u003emdio_bus_data-\u003eneeds_reset = true;\n }\n \n+static int intel_mgbe_pcs_init(struct stmmac_priv *priv)\n+{\n+\tstruct fwnode_handle *devnode, *pcsnode;\n+\tstruct dw_xpcs *xpcs;\n+\tint addr;\n+\n+\tdevnode = dev_fwnode(priv-\u003edevice);\n+\n+\tif (fwnode_property_present(devnode, \"pcs-handle\")) {\n+\t\tpcsnode = fwnode_find_reference(devnode, \"pcs-handle\", 0);\n+\t\txpcs = xpcs_create_fwnode(pcsnode);\n+\t\tfwnode_handle_put(pcsnode);\n+\t} else {\n+\t\taddr = ffs(priv-\u003eplat-\u003emdio_bus_data-\u003epcs_mask) - 1;\n+\t\txpcs = xpcs_create_mdiodev(priv-\u003emii, addr);\n+\t}\n+\n+\tif (IS_ERR(xpcs))\n+\t\treturn PTR_ERR(xpcs);\n+\n+\txpcs_config_eee_mult_fact(xpcs, priv-\u003eplat-\u003emult_fact_100ns);\n+\n+\tpriv-\u003ehw-\u003expcs = xpcs;\n+\treturn 0;\n+}\n+\n+static void intel_mgbe_pcs_exit(struct stmmac_priv *priv)\n+{\n+\tif (!priv-\u003ehw-\u003expcs)\n+\t\treturn;\n+\n+\txpcs_destroy(priv-\u003ehw-\u003expcs);\n+\tpriv-\u003ehw-\u003expcs = NULL;\n+}\n+\n static struct phylink_pcs *intel_mgbe_select_pcs(struct stmmac_priv *priv,\n \t\t\t\t\t\t phy_interface_t interface)\n {\n-\t/* plat-\u003emdio_bus_data-\u003ehas_xpcs has been set true, so there\n-\t * should always be an XPCS. The original code would always\n-\t * return this if present.\n-\t */\n+\tif (!priv-\u003ehw-\u003expcs)\n+\t\treturn NULL;\n+\n \treturn xpcs_to_phylink_pcs(priv-\u003ehw-\u003expcs);\n }\n \n@@ -733,6 +767,8 @@ static int intel_mgbe_common_data(struct pci_dev *pdev,\n \t    plat-\u003ephy_interface == PHY_INTERFACE_MODE_1000BASEX) {\n \t\tplat-\u003emdio_bus_data-\u003epcs_mask = BIT_U32(INTEL_MGBE_XPCS_ADDR);\n \t\tplat-\u003edefault_an_inband = true;\n+\t\tplat-\u003epcs_init = intel_mgbe_pcs_init;\n+\t\tplat-\u003epcs_exit = intel_mgbe_pcs_exit;\n \t\tplat-\u003eselect_pcs = intel_mgbe_select_pcs;\n \t}\n \ndiff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c\nindex 19f34e18bfef2..9af32c26f9c14 100644\n--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c\n+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c\n@@ -81,8 +81,11 @@ static int renesas_gmac_pcs_init(struct stmmac_priv *priv)\n \n static void renesas_gmac_pcs_exit(struct stmmac_priv *priv)\n {\n-\tif (priv-\u003ehw-\u003ephylink_pcs)\n-\t\tmiic_destroy(priv-\u003ehw-\u003ephylink_pcs);\n+\tif (!priv-\u003ehw-\u003ephylink_pcs)\n+\t\treturn;\n+\n+\tmiic_destroy(priv-\u003ehw-\u003ephylink_pcs);\n+\tpriv-\u003ehw-\u003ephylink_pcs = NULL;\n }\n \n static struct phylink_pcs *renesas_gmac_select_pcs(struct stmmac_priv *priv,\ndiff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c\nindex 8d7042e689261..b3d746dd966ff 100644\n--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c\n+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c\n@@ -20,6 +20,7 @@\n #include \u003clinux/delay.h\u003e\n #include \u003clinux/mfd/syscon.h\u003e\n #include \u003clinux/regmap.h\u003e\n+#include \u003clinux/pcs/pcs-xpcs-rk.h\u003e\n #include \u003clinux/pm_runtime.h\u003e\n \n #include \"stmmac_platform.h\"\n@@ -47,6 +48,7 @@ struct rk_gmac_ops {\n \tvoid (*set_to_rgmii)(struct rk_priv_data *bsp_priv,\n \t\t\t     int tx_delay, int rx_delay);\n \tvoid (*set_to_rmii)(struct rk_priv_data *bsp_priv);\n+\tvoid (*set_to_sgmii)(struct rk_priv_data *bsp_priv);\n \tint (*set_speed)(struct rk_priv_data *bsp_priv,\n \t\t\t phy_interface_t interface, int speed);\n \tvoid (*integrated_phy_powerup)(struct rk_priv_data *bsp_priv);\n@@ -63,6 +65,7 @@ struct rk_gmac_ops {\n \tbool clock_grf_reg_in_php;\n \tbool supports_rgmii;\n \tbool supports_rmii;\n+\tbool supports_sgmii;\n \tbool php_grf_required;\n \tbool regs_valid;\n \tu32 regs[];\n@@ -98,6 +101,7 @@ struct rk_priv_data {\n \tbool integrated_phy;\n \tbool supports_rgmii;\n \tbool supports_rmii;\n+\tbool supports_sgmii;\n \n \tstruct clk_bulk_data *clks;\n \tint num_clks;\n@@ -809,6 +813,8 @@ static const struct rk_gmac_ops rk3528_ops = {\n #define RK3568_GRF_GMAC1_CON1\t\t0x038c\n \n /* RK3568_GRF_GMAC0_CON1 \u0026\u0026 RK3568_GRF_GMAC1_CON1 */\n+#define RK3568_GMAC_MODE_RMII_RGMII\t\tGRF_CLR_BIT(7)\n+#define RK3568_GMAC_MODE_SGMII_QSGMII\t\tGRF_BIT(7)\n #define RK3568_GMAC_FLOW_CTRL\t\t\tGRF_BIT(3)\n #define RK3568_GMAC_FLOW_CTRL_CLR\t\tGRF_CLR_BIT(3)\n #define RK3568_GMAC_RXCLK_DLY_ENABLE\t\tGRF_BIT(1)\n@@ -836,6 +842,16 @@ static int rk3568_init(struct rk_priv_data *bsp_priv)\n \t}\n }\n \n+static void rk3568_set_to_rmii(struct rk_priv_data *bsp_priv)\n+{\n+\tu32 con1;\n+\n+\tcon1 = (bsp_priv-\u003eid == 1) ? RK3568_GRF_GMAC1_CON1 :\n+\t\t\t\t     RK3568_GRF_GMAC0_CON1;\n+\n+\tregmap_write(bsp_priv-\u003egrf, con1, RK3568_GMAC_MODE_RMII_RGMII);\n+}\n+\n static void rk3568_set_to_rgmii(struct rk_priv_data *bsp_priv,\n \t\t\t\tint tx_delay, int rx_delay)\n {\n@@ -851,19 +867,31 @@ static void rk3568_set_to_rgmii(struct rk_priv_data *bsp_priv,\n \t\t     RK3568_GMAC_CLK_TX_DL_CFG(tx_delay));\n \n \tregmap_write(bsp_priv-\u003egrf, con1,\n+\t\t     RK3568_GMAC_MODE_RMII_RGMII |\n \t\t     RK3568_GMAC_RXCLK_DLY_ENABLE |\n \t\t     RK3568_GMAC_TXCLK_DLY_ENABLE);\n }\n \n+static void rk3568_set_to_sgmii(struct rk_priv_data *bsp_priv)\n+{\n+\tu32 con1;\n+\n+\tcon1 = (bsp_priv-\u003eid == 1) ? RK3568_GRF_GMAC1_CON1 :\n+\t\t\t\t     RK3568_GRF_GMAC0_CON1;\n+\n+\tregmap_write(bsp_priv-\u003egrf, con1, RK3568_GMAC_MODE_SGMII_QSGMII);\n+}\n+\n static const struct rk_gmac_ops rk3568_ops = {\n \t.init = rk3568_init,\n+\t.set_to_rmii = rk3568_set_to_rmii,\n \t.set_to_rgmii = rk3568_set_to_rgmii,\n+\t.set_to_sgmii = rk3568_set_to_sgmii,\n+\n \t.set_speed = rk_set_clk_mac_speed,\n \n \t.gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),\n \n-\t.supports_rmii = true,\n-\n \t.regs_valid = true,\n \t.regs = {\n \t\t0xfe2a0000, /* gmac0 */\n@@ -1208,6 +1236,43 @@ static void rk_phy_powerdown(struct rk_priv_data *bsp_priv)\n \t\tdev_err(bsp_priv-\u003edev, \"fail to disable phy-supply\\n\");\n }\n \n+static int rk_pcs_init(struct stmmac_priv *priv)\n+{\n+\tstruct device_node *np = priv-\u003edevice-\u003eof_node;\n+\tstruct device_node *pcs_node;\n+\tstruct dw_xpcs *xpcs;\n+\n+\tpcs_node = of_parse_phandle(np, \"pcs-handle\", 0);\n+\tif (!pcs_node)\n+\t\treturn -ENODEV;\n+\n+\txpcs = xpcs_rk_create(priv-\u003edevice, pcs_node);\n+\tof_node_put(pcs_node);\n+\tif (IS_ERR(xpcs))\n+\t\treturn PTR_ERR(xpcs);\n+\n+\tpriv-\u003ehw-\u003expcs = xpcs;\n+\treturn 0;\n+}\n+\n+static void rk_pcs_exit(struct stmmac_priv *priv)\n+{\n+\tif (!priv-\u003ehw-\u003expcs)\n+\t\treturn;\n+\n+\txpcs_destroy(priv-\u003ehw-\u003expcs);\n+\tpriv-\u003ehw-\u003expcs = NULL;\n+}\n+\n+static struct phylink_pcs *rk_select_pcs(struct stmmac_priv *priv,\n+\t\t\t\t\t phy_interface_t interface)\n+{\n+\tif (!priv-\u003ehw-\u003expcs)\n+\t\treturn NULL;\n+\n+\treturn xpcs_to_phylink_pcs(priv-\u003ehw-\u003expcs);\n+}\n+\n static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev,\n \t\t\t\t\t  struct plat_stmmacenet_data *plat,\n \t\t\t\t\t  const struct rk_gmac_ops *ops)\n@@ -1330,6 +1395,7 @@ static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev,\n \n \tbsp_priv-\u003esupports_rgmii = ops-\u003esupports_rgmii || !!ops-\u003eset_to_rgmii;\n \tbsp_priv-\u003esupports_rmii = ops-\u003esupports_rmii || !!ops-\u003eset_to_rmii;\n+\tbsp_priv-\u003esupports_sgmii = ops-\u003esupports_sgmii || !!ops-\u003eset_to_sgmii;\n \n \tif (ops-\u003einit) {\n \t\tret = ops-\u003einit(bsp_priv);\n@@ -1361,6 +1427,10 @@ static int rk_gmac_check_ops(struct rk_priv_data *bsp_priv)\n \t\tif (!bsp_priv-\u003esupports_rmii)\n \t\t\treturn -EINVAL;\n \t\tbreak;\n+\tcase PHY_INTERFACE_MODE_SGMII:\n+\t\tif (!bsp_priv-\u003esupports_sgmii)\n+\t\t\treturn -EINVAL;\n+\t\tbreak;\n \tdefault:\n \t\tdev_err(bsp_priv-\u003edev,\n \t\t\t\"unsupported interface %d\", bsp_priv-\u003ephy_iface);\n@@ -1379,16 +1449,19 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)\n \tif (ret)\n \t\treturn ret;\n \n+\tret = gmac_clk_enable(bsp_priv, true);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (bsp_priv-\u003ephy_iface == PHY_INTERFACE_MODE_SGMII)\n+\t\tgoto set_mode;\n+\n \tret = rk_get_phy_intf_sel(bsp_priv-\u003ephy_iface);\n \tif (ret \u003c 0)\n-\t\treturn ret;\n+\t\tgoto clk_disable;\n \n \tintf = ret;\n \n-\tret = gmac_clk_enable(bsp_priv, true);\n-\tif (ret)\n-\t\treturn ret;\n-\n \tif (bsp_priv-\u003egmac_phy_intf_sel_mask ||\n \t    bsp_priv-\u003egmac_rmii_mode_mask) {\n \t\t/* If defined, encode the phy_intf_sel value */\n@@ -1399,10 +1472,8 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)\n \t\t\t\t      bsp_priv-\u003egmac_rmii_mode_mask);\n \n \t\tret = rk_write_gmac_grf_reg(bsp_priv, val);\n-\t\tif (ret \u003c 0) {\n-\t\t\tgmac_clk_enable(bsp_priv, false);\n-\t\t\treturn ret;\n-\t\t}\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto clk_disable;\n \t}\n \n \tif (bsp_priv-\u003eclock.rmii_mode_mask) {\n@@ -1410,13 +1481,12 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)\n \t\t\t\t     bsp_priv-\u003eclock.rmii_mode_mask);\n \n \t\tret = rk_write_clock_grf_reg(bsp_priv, val);\n-\t\tif (ret \u003c 0) {\n-\t\t\tgmac_clk_enable(bsp_priv, false);\n-\t\t\treturn ret;\n-\t\t}\n+\t\tif (ret \u003c 0)\n+\t\t\tgoto clk_disable;\n \t}\n \n-\t/*rmii or rgmii*/\n+set_mode:\n+\t/* rmii, rgmii, sgmii */\n \tswitch (bsp_priv-\u003ephy_iface) {\n \tcase PHY_INTERFACE_MODE_RGMII:\n \t\tdev_info(dev, \"init for RGMII\\n\");\n@@ -1447,15 +1517,20 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)\n \t\tif (bsp_priv-\u003eops-\u003eset_to_rmii)\n \t\t\tbsp_priv-\u003eops-\u003eset_to_rmii(bsp_priv);\n \t\tbreak;\n+\tcase PHY_INTERFACE_MODE_SGMII:\n+\t\tdev_info(dev, \"init for SGMII\\n\");\n+\t\tif (bsp_priv-\u003eops-\u003eset_to_sgmii)\n+\t\t\tbsp_priv-\u003eops-\u003eset_to_sgmii(bsp_priv);\n+\t\tbreak;\n \tdefault:\n \t\tdev_err(dev, \"NO interface defined!\\n\");\n+\t\tret = -EINVAL;\n+\t\tgoto clk_disable;\n \t}\n \n \tret = rk_phy_powerup(bsp_priv);\n-\tif (ret) {\n-\t\tgmac_clk_enable(bsp_priv, false);\n-\t\treturn ret;\n-\t}\n+\tif (ret)\n+\t\tgoto clk_disable;\n \n \tpm_runtime_get_sync(dev);\n \n@@ -1463,6 +1538,10 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)\n \t\tbsp_priv-\u003eops-\u003eintegrated_phy_powerup(bsp_priv);\n \n \treturn 0;\n+\n+clk_disable:\n+\tgmac_clk_enable(bsp_priv, false);\n+\treturn ret;\n }\n \n static void rk_gmac_powerdown(struct rk_priv_data *gmac)\n@@ -1486,6 +1565,9 @@ static void rk_get_interfaces(struct stmmac_priv *priv, void *bsp_priv,\n \n \tif (rk-\u003esupports_rmii)\n \t\t__set_bit(PHY_INTERFACE_MODE_RMII, interfaces);\n+\n+\tif (rk-\u003esupports_sgmii)\n+\t\t__set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);\n }\n \n static int rk_set_clk_tx_rate(void *bsp_priv_, struct clk *clk_tx_i,\n@@ -1602,6 +1684,17 @@ static int rk_gmac_probe(struct platform_device *pdev)\n \tplat_dat-\u003esuspend = rk_gmac_suspend;\n \tplat_dat-\u003eresume = rk_gmac_resume;\n \n+\tif (plat_dat-\u003ephy_interface == PHY_INTERFACE_MODE_SGMII) {\n+\t\t/* SGMII clock always runs at 125 MHz */\n+\t\tplat_dat-\u003eset_clk_tx_rate = NULL;\n+\n+\t\t/* SGMII requires a PCS */\n+\t\tplat_dat-\u003edefault_an_inband = true;\n+\t\tplat_dat-\u003epcs_init = rk_pcs_init;\n+\t\tplat_dat-\u003epcs_exit = rk_pcs_exit;\n+\t\tplat_dat-\u003eselect_pcs = rk_select_pcs;\n+\t}\n+\n \tplat_dat-\u003ebsp_priv = rk_gmac_setup(pdev, plat_dat, data);\n \tif (IS_ERR(plat_dat-\u003ebsp_priv))\n \t\treturn PTR_ERR(plat_dat-\u003ebsp_priv);\ndiff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c\nindex 13634965bc19a..01df4776edb3f 100644\n--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c\n+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c\n@@ -35,8 +35,11 @@ static int rzn1_dwmac_pcs_init(struct stmmac_priv *priv)\n \n static void rzn1_dwmac_pcs_exit(struct stmmac_priv *priv)\n {\n-\tif (priv-\u003ehw-\u003ephylink_pcs)\n-\t\tmiic_destroy(priv-\u003ehw-\u003ephylink_pcs);\n+\tif (!priv-\u003ehw-\u003ephylink_pcs)\n+\t\treturn;\n+\n+\tmiic_destroy(priv-\u003ehw-\u003ephylink_pcs);\n+\tpriv-\u003ehw-\u003ephylink_pcs = NULL;\n }\n \n static struct phylink_pcs *rzn1_dwmac_select_pcs(struct stmmac_priv *priv,\ndiff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c\nindex 1d7f0a57d2889..6d4bc1fe8f751 100644\n--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c\n+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c\n@@ -539,8 +539,11 @@ static int socfpga_dwmac_pcs_init(struct stmmac_priv *priv)\n \n static void socfpga_dwmac_pcs_exit(struct stmmac_priv *priv)\n {\n-\tif (priv-\u003ehw-\u003ephylink_pcs)\n-\t\tlynx_pcs_destroy(priv-\u003ehw-\u003ephylink_pcs);\n+\tif (!priv-\u003ehw-\u003ephylink_pcs)\n+\t\treturn;\n+\n+\tlynx_pcs_destroy(priv-\u003ehw-\u003ephylink_pcs);\n+\tpriv-\u003ehw-\u003ephylink_pcs = NULL;\n }\n \n static struct phylink_pcs *socfpga_dwmac_select_pcs(struct stmmac_priv *priv,\ndiff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c\nindex afe98ff5bdcb0..7396b68899c66 100644\n--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c\n+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c\n@@ -426,35 +426,14 @@ int stmmac_mdio_reset(struct mii_bus *bus)\n int stmmac_pcs_setup(struct net_device *ndev)\n {\n \tstruct stmmac_priv *priv = netdev_priv(ndev);\n-\tstruct fwnode_handle *devnode, *pcsnode;\n-\tstruct dw_xpcs *xpcs = NULL;\n-\tint addr, ret;\n-\n-\tdevnode = dev_fwnode(priv-\u003edevice);\n-\n-\tif (priv-\u003eplat-\u003epcs_init) {\n-\t\tret = priv-\u003eplat-\u003epcs_init(priv);\n-\t} else if (fwnode_property_present(devnode, \"pcs-handle\")) {\n-\t\tpcsnode = fwnode_find_reference(devnode, \"pcs-handle\", 0);\n-\t\txpcs = xpcs_create_fwnode(pcsnode);\n-\t\tfwnode_handle_put(pcsnode);\n-\t\tret = PTR_ERR_OR_ZERO(xpcs);\n-\t} else if (priv-\u003eplat-\u003emdio_bus_data \u0026\u0026\n-\t\t   priv-\u003eplat-\u003emdio_bus_data-\u003epcs_mask) {\n-\t\taddr = ffs(priv-\u003eplat-\u003emdio_bus_data-\u003epcs_mask) - 1;\n-\t\txpcs = xpcs_create_mdiodev(priv-\u003emii, addr);\n-\t\tret = PTR_ERR_OR_ZERO(xpcs);\n-\t} else {\n+\tint ret;\n+\n+\tif (!priv-\u003eplat-\u003epcs_init)\n \t\treturn 0;\n-\t}\n \n+\tret = priv-\u003eplat-\u003epcs_init(priv);\n \tif (ret)\n-\t\treturn dev_err_probe(priv-\u003edevice, ret, \"No xPCS found\\n\");\n-\n-\tif (xpcs)\n-\t\txpcs_config_eee_mult_fact(xpcs, priv-\u003eplat-\u003emult_fact_100ns);\n-\n-\tpriv-\u003ehw-\u003expcs = xpcs;\n+\t\treturn dev_err_probe(priv-\u003edevice, ret, \"Failed to initialize PCS\\n\");\n \n \treturn 0;\n }\n@@ -463,14 +442,10 @@ void stmmac_pcs_clean(struct net_device *ndev)\n {\n \tstruct stmmac_priv *priv = netdev_priv(ndev);\n \n-\tif (priv-\u003eplat-\u003epcs_exit)\n-\t\tpriv-\u003eplat-\u003epcs_exit(priv);\n-\n-\tif (!priv-\u003ehw-\u003expcs)\n+\tif (!priv-\u003eplat-\u003epcs_exit)\n \t\treturn;\n \n-\txpcs_destroy(priv-\u003ehw-\u003expcs);\n-\tpriv-\u003ehw-\u003expcs = NULL;\n+\tpriv-\u003eplat-\u003epcs_exit(priv);\n }\n \n struct stmmac_clk_rate {\ndiff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig\nindex e417fd66f660a..3db9b343ac0c7 100644\n--- a/drivers/net/pcs/Kconfig\n+++ b/drivers/net/pcs/Kconfig\n@@ -12,6 +12,31 @@ config PCS_XPCS\n \t  This module provides a driver and helper functions for Synopsys\n \t  DesignWare XPCS controllers.\n \n+if PCS_XPCS\n+\n+config PCS_XPCS_PLATFORM\n+\ttristate \"Generic XPCS controller support\"\n+\tdefault PCS_XPCS\n+\thelp\n+\t  Generic DWXPCS driver for platforms that don't require any\n+\t  platform specific code to function or is using platform\n+\t  data for setup.\n+\n+\t  If you have a controller with this interface, say Y or M here.\n+\n+config PCS_XPCS_ROCKCHIP\n+\ttristate \"Rockchip XPCS controller support\"\n+\tdefault ARCH_ROCKCHIP\n+\tdepends on OF \u0026\u0026 (ARCH_ROCKCHIP || COMPILE_TEST)\n+\tselect GENERIC_PHY\n+\tselect PM_GENERIC_DOMAINS\n+\thelp\n+\t  Support for XPCS controller on Rockchip RK356x SoC.\n+\n+\t  If you have a Rockchip SoC with this interface, say Y or M here.\n+\n+endif # PCS_XPCS\n+\n config PCS_LYNX\n \ttristate\n \thelp\ndiff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile\nindex 4f7920618b900..f9f6cf2578d72 100644\n--- a/drivers/net/pcs/Makefile\n+++ b/drivers/net/pcs/Makefile\n@@ -1,10 +1,11 @@\n # SPDX-License-Identifier: GPL-2.0\n # Makefile for Linux PCS drivers\n \n-pcs_xpcs-$(CONFIG_PCS_XPCS)\t:= pcs-xpcs.o pcs-xpcs-plat.o \\\n-\t\t\t\t   pcs-xpcs-nxp.o pcs-xpcs-wx.o\n+pcs_xpcs-$(CONFIG_PCS_XPCS)\t:= pcs-xpcs.o pcs-xpcs-nxp.o pcs-xpcs-wx.o\n \n obj-$(CONFIG_PCS_XPCS)\t\t+= pcs_xpcs.o\n+obj-$(CONFIG_PCS_XPCS_PLATFORM) += pcs-xpcs-plat.o\n+obj-$(CONFIG_PCS_XPCS_ROCKCHIP) += pcs-xpcs-rk.o\n obj-$(CONFIG_PCS_LYNX)\t\t+= pcs-lynx.o\n obj-$(CONFIG_PCS_MTK_LYNXI)\t+= pcs-mtk-lynxi.o\n obj-$(CONFIG_PCS_RZN1_MIIC)\t+= pcs-rzn1-miic.o\ndiff --git a/drivers/net/pcs/pcs-xpcs-rk.c b/drivers/net/pcs/pcs-xpcs-rk.c\nnew file mode 100644\nindex 0000000000000..d5e0f22c973c2\n--- /dev/null\n+++ b/drivers/net/pcs/pcs-xpcs-rk.c\n@@ -0,0 +1,573 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/*\n+ * Rockchip XPCS platform device driver\n+ *\n+ * Based on the Synopsys DesignWare XPCS platform driver.\n+ * Copyright (C) 2024 Serge Semin\n+ *\n+ * Adapted for Rockchip SoCs, with reference to the Rockchip OEM driver.\n+ * Copyright (C) 2026 Coia Prant\n+ */\n+\n+#include \u003clinux/atomic.h\u003e\n+#include \u003clinux/bitfield.h\u003e\n+#include \u003clinux/clk.h\u003e\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/io.h\u003e\n+#include \u003clinux/iopoll.h\u003e\n+#include \u003clinux/math.h\u003e\n+#include \u003clinux/mdio.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/of_platform.h\u003e\n+#include \u003clinux/pcs/pcs-xpcs-rk.h\u003e\n+#include \u003clinux/phy.h\u003e\n+#include \u003clinux/phy/phy.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003clinux/pm_domain.h\u003e\n+#include \u003clinux/pm_runtime.h\u003e\n+#include \u003clinux/property.h\u003e\n+#include \u003clinux/sizes.h\u003e\n+#include \u003clinux/time.h\u003e\n+\n+#include \"pcs-xpcs.h\"\n+\n+struct dw_xpcs_rk {\n+\tstruct platform_device *pdev;\n+\tstruct mii_bus *bus;\n+\tvoid __iomem *reg_base;\n+\tstruct phy *serdes_phy;\n+\tstruct clk *csr_clk;\n+\tstruct clk *eee_clk;\n+\tu8 eee_mult_fact;\n+};\n+\n+static ptrdiff_t xpcs_rk_addr_format(int dev, int reg)\n+{\n+\treturn FIELD_PREP(0x70000, dev) | FIELD_PREP(0xffff, reg);\n+}\n+\n+static int xpcs_rk_read_reg(struct dw_xpcs_rk *pxpcs, int dev, int reg)\n+{\n+\tptrdiff_t csr;\n+\tint ret;\n+\n+\tcsr = xpcs_rk_addr_format(dev, reg);\n+\n+\tret = pm_runtime_resume_and_get(\u0026pxpcs-\u003epdev-\u003edev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = readl(pxpcs-\u003ereg_base + (csr \u003c\u003c 2)) \u0026 0xffff;\n+\n+\tpm_runtime_put(\u0026pxpcs-\u003epdev-\u003edev);\n+\treturn ret;\n+}\n+\n+static int xpcs_rk_write_reg(struct dw_xpcs_rk *pxpcs, int dev, int reg, u16 val)\n+{\n+\tptrdiff_t csr;\n+\tint ret;\n+\n+\tcsr = xpcs_rk_addr_format(dev, reg);\n+\n+\tret = pm_runtime_resume_and_get(\u0026pxpcs-\u003epdev-\u003edev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\twritel(val, pxpcs-\u003ereg_base + (csr \u003c\u003c 2));\n+\n+\tpm_runtime_put(\u0026pxpcs-\u003epdev-\u003edev);\n+\treturn 0;\n+}\n+\n+#define ROCKCHIP_MMD_MII1\t2\n+#define ROCKCHIP_MMD_MII2\t3\n+#define ROCKCHIP_MMD_MII3\t4\n+#define ROCKCHIP_MMD_PMAPMD\t6\n+#define ROCKCHIP_MMD_MII\t7\n+\n+static bool xpcs_rk_mdio_addr_validate(int addr)\n+{\n+\treturn !(addr \u003c 0 || addr \u003e 3);\n+}\n+\n+static int xpcs_rk_mdio_read_remapping(int addr, int dev, int reg)\n+{\n+\tswitch (dev) {\n+\tcase MDIO_MMD_PMAPMD:\n+\t\treturn ROCKCHIP_MMD_PMAPMD;\n+\tcase MDIO_MMD_VEND2:\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -ENXIO;\n+\t}\n+\n+\t/*\n+\t * Reads are redirected by hardware to the port's read-only mirror;\n+\t * only writes have to be targeted at MII (see the write path).\n+\t */\n+\tswitch (addr) {\n+\tcase 0:\n+\t\treturn ROCKCHIP_MMD_MII;\n+\tcase 1:\n+\t\treturn ROCKCHIP_MMD_MII1;\n+\tcase 2:\n+\t\treturn ROCKCHIP_MMD_MII2;\n+\tcase 3:\n+\t\treturn ROCKCHIP_MMD_MII3;\n+\tdefault:\n+\t\treturn -ENODEV;\n+\t}\n+}\n+\n+static int xpcs_rk_mdio_write_remapping(int addr, int dev, int reg)\n+{\n+\tswitch (dev) {\n+\tcase MDIO_MMD_PMAPMD:\n+\t\treturn ROCKCHIP_MMD_PMAPMD;\n+\tcase MDIO_MMD_VEND2:\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -ENXIO;\n+\t}\n+\n+\t/*\n+\t * These registers physically live only in MII (the management port).\n+\t * Ports 1-3 expose read-only mirrors of these bits, so writes must\n+\t * always target MII; the read path remaps per address and the\n+\t * hardware redirects to the port's mirror.\n+\t */\n+\tswitch (reg) {\n+\tcase DW_VR_MII_AN_CTRL:\n+\tcase DW_VR_MII_AN_INTR_STS:\n+\tcase DW_VR_MII_EEE_MCTRL0:\n+\tcase DW_VR_MII_EEE_MCTRL1:\n+\tcase DW_VR_MII_DIG_CTRL2:\n+\t\treturn ROCKCHIP_MMD_MII;\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\tswitch (addr) {\n+\tcase 0:\n+\t\treturn ROCKCHIP_MMD_MII;\n+\tcase 1:\n+\t\treturn ROCKCHIP_MMD_MII1;\n+\tcase 2:\n+\t\treturn ROCKCHIP_MMD_MII2;\n+\tcase 3:\n+\t\treturn ROCKCHIP_MMD_MII3;\n+\tdefault:\n+\t\treturn -ENODEV;\n+\t}\n+}\n+\n+static int xpcs_rk_read_c22(struct mii_bus *bus, int addr, int reg)\n+{\n+\tstruct dw_xpcs_rk *pxpcs = bus-\u003epriv;\n+\tint dev;\n+\n+\tif (!xpcs_rk_mdio_addr_validate(addr))\n+\t\treturn -ENODEV;\n+\n+\tdev = xpcs_rk_mdio_read_remapping(addr, MDIO_MMD_VEND2, reg);\n+\tif (dev \u003c 0)\n+\t\treturn 0xffff;\n+\n+\treturn xpcs_rk_read_reg(pxpcs, dev, reg);\n+}\n+\n+static int xpcs_rk_write_c22(struct mii_bus *bus, int addr, int reg, u16 val)\n+{\n+\tstruct dw_xpcs_rk *pxpcs = bus-\u003epriv;\n+\tint dev;\n+\n+\tif (!xpcs_rk_mdio_addr_validate(addr))\n+\t\treturn -ENODEV;\n+\n+\tdev = xpcs_rk_mdio_write_remapping(addr, MDIO_MMD_VEND2, reg);\n+\tif (dev \u003c 0)\n+\t\treturn 0;\n+\n+\treturn xpcs_rk_write_reg(pxpcs, dev, reg, val);\n+}\n+\n+static int xpcs_rk_read_c45(struct mii_bus *bus, int addr, int dev, int reg)\n+{\n+\tstruct dw_xpcs_rk *pxpcs = bus-\u003epriv;\n+\n+\tif (!xpcs_rk_mdio_addr_validate(addr))\n+\t\treturn -ENODEV;\n+\n+\tdev = xpcs_rk_mdio_read_remapping(addr, dev, reg);\n+\tif (dev \u003c 0)\n+\t\treturn 0xffff;\n+\n+\treturn xpcs_rk_read_reg(pxpcs, dev, reg);\n+}\n+\n+static int xpcs_rk_write_c45(struct mii_bus *bus, int addr, int dev, int reg, u16 val)\n+{\n+\tstruct dw_xpcs_rk *pxpcs = bus-\u003epriv;\n+\n+\tif (!xpcs_rk_mdio_addr_validate(addr))\n+\t\treturn -ENODEV;\n+\n+\tdev = xpcs_rk_mdio_write_remapping(addr, dev, reg);\n+\tif (dev \u003c 0)\n+\t\treturn 0;\n+\n+\treturn xpcs_rk_write_reg(pxpcs, dev, reg, val);\n+}\n+\n+static struct dw_xpcs_rk *xpcs_rk_create_data(struct platform_device *pdev)\n+{\n+\tstruct dw_xpcs_rk *pxpcs;\n+\n+\tpxpcs = devm_kzalloc(\u0026pdev-\u003edev, sizeof(*pxpcs), GFP_KERNEL);\n+\tif (!pxpcs)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tpxpcs-\u003epdev = pdev;\n+\n+\tdev_set_drvdata(\u0026pdev-\u003edev, pxpcs);\n+\n+\treturn pxpcs;\n+}\n+\n+static int xpcs_rk_serdes_phy_init(struct dw_xpcs_rk *pxpcs)\n+{\n+\tstruct device *dev = \u0026pxpcs-\u003epdev-\u003edev;\n+\n+\tpxpcs-\u003eserdes_phy = devm_phy_get(dev, \"serdes\");\n+\tif (IS_ERR(pxpcs-\u003eserdes_phy))\n+\t\treturn dev_err_probe(dev, PTR_ERR(pxpcs-\u003eserdes_phy),\n+\t\t\t\t\t\"Failed to get SerDes PHY\\n\");\n+\n+\treturn 0;\n+}\n+\n+static void xpcs_rk_serdes_phy_poweroff(void *data)\n+{\n+\tstruct dw_xpcs_rk *pxpcs = data;\n+\tstruct device *dev = \u0026pxpcs-\u003epdev-\u003edev;\n+\n+\tphy_power_off(pxpcs-\u003eserdes_phy);\n+\tphy_exit(pxpcs-\u003eserdes_phy);\n+\n+\tdev_pm_genpd_rpm_always_on(dev, false);\n+}\n+\n+static int xpcs_rk_serdes_phy_poweron(struct dw_xpcs_rk *pxpcs)\n+{\n+\tstruct device *dev = \u0026pxpcs-\u003epdev-\u003edev;\n+\tint ret;\n+\n+\t/*\n+\t * The power domain is required and must be enabled, which allows us to\n+\t * dynamically turn the CSR clock on/off using PM while keeping the PCS\n+\t * powered on.\n+\t */\n+\tret = dev_pm_genpd_rpm_always_on(dev, true);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to power on power-domains\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tret = phy_init(pxpcs-\u003eserdes_phy);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to init SerDes PHY\\n\");\n+\t\tgoto pm_domain;\n+\t}\n+\n+\tret = phy_power_on(pxpcs-\u003eserdes_phy);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to power on SerDes PHY\\n\");\n+\t\tgoto serdes_phy;\n+\t}\n+\n+\tret = devm_add_action_or_reset(dev, xpcs_rk_serdes_phy_poweroff, pxpcs);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to register devm for SerDes PHY: %d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+\n+serdes_phy:\n+\tphy_exit(pxpcs-\u003eserdes_phy);\n+pm_domain:\n+\tdev_pm_genpd_rpm_always_on(dev, false);\n+\treturn ret;\n+}\n+\n+static int xpcs_rk_init_res(struct dw_xpcs_rk *pxpcs)\n+{\n+\tstruct platform_device *pdev = pxpcs-\u003epdev;\n+\tstruct device *dev = \u0026pdev-\u003edev;\n+\tstruct resource *res;\n+\n+\tres = platform_get_resource(pdev, IORESOURCE_MEM, 0);\n+\tif (!res) {\n+\t\tdev_err(dev, \"No reg-space found\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (resource_size(res) \u003c SZ_2M) {\n+\t\tdev_err(dev, \"Invalid reg-space size\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tpxpcs-\u003ereg_base = devm_ioremap_resource(dev, res);\n+\tif (IS_ERR(pxpcs-\u003ereg_base)) {\n+\t\tdev_err(dev, \"Failed to map reg-space\\n\");\n+\t\treturn PTR_ERR(pxpcs-\u003ereg_base);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void xpcs_rk_exit_clk(void *data)\n+{\n+\tstruct dw_xpcs_rk *pxpcs = data;\n+\tstruct device *dev = \u0026pxpcs-\u003epdev-\u003edev;\n+\n+\tpm_runtime_force_suspend(dev);\n+\tclk_disable_unprepare(pxpcs-\u003eeee_clk);\n+}\n+\n+static int xpcs_rk_init_clk(struct dw_xpcs_rk *pxpcs)\n+{\n+\tstruct device *dev = \u0026pxpcs-\u003epdev-\u003edev;\n+\tunsigned long rate;\n+\tu64 mult;\n+\tint ret;\n+\n+\tpxpcs-\u003ecsr_clk = devm_clk_get(dev, \"csr\");\n+\tif (IS_ERR(pxpcs-\u003ecsr_clk))\n+\t\treturn dev_err_probe(dev, PTR_ERR(pxpcs-\u003ecsr_clk),\n+\t\t\t\t\t \"Failed to get CSR clock\\n\");\n+\n+\tpxpcs-\u003eeee_clk = devm_clk_get(dev, \"eee\");\n+\tif (IS_ERR(pxpcs-\u003eeee_clk))\n+\t\treturn dev_err_probe(dev, PTR_ERR(pxpcs-\u003eeee_clk),\n+\t\t\t\t\t \"Failed to get EEE clock\\n\");\n+\n+\tret = clk_prepare_enable(pxpcs-\u003eeee_clk);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to enable EEE clock\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tpm_runtime_set_suspended(dev);\n+\tpm_runtime_enable(dev);\n+\n+\tret = devm_add_action_or_reset(dev, xpcs_rk_exit_clk, pxpcs);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to register devm for EEE clock: %d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Compute the multiplier for the EEE clock so that\n+\t * clk_eee_period * (mult_fact + 1) falls within 80..120 ns.\n+\t *\n+\t * On RK3568, clk_xpcs_eee is muxed between gpll200 (200 MHz, 5 ns)\n+\t * and cpll125 (125 MHz, 8 ns), selected by CRU_CLKSEL_CON29 bit 13.\n+\t * The reset value is 0 (200 MHz), but derive the value at runtime to\n+\t * stay correct if the mux is changed by a board.\n+\t *\n+\t * Use a 64-bit intermediate: on 32-bit builds, 100 * 200000000\n+\t * does not fit in unsigned long. Clamp to the 4-bit\n+\t * DW_VR_MII_EEE_MULT_FACT_100NS field.\n+\t */\n+\trate = clk_get_rate(pxpcs-\u003eeee_clk);\n+\tif (!rate)\n+\t\treturn dev_err_probe(dev, -EINVAL, \"Invalid EEE clock rate\\n\");\n+\n+\tmult = DIV_ROUND_CLOSEST_ULL(100ULL * rate, NSEC_PER_SEC) - 1;\n+\tpxpcs-\u003eeee_mult_fact = min_t(u64, mult, 15);\n+\treturn 0;\n+}\n+\n+static int xpcs_rk_init_bus(struct dw_xpcs_rk *pxpcs)\n+{\n+\tstruct device *dev = \u0026pxpcs-\u003epdev-\u003edev;\n+\tstatic atomic_t id = ATOMIC_INIT(-1);\n+\tstruct mii_bus *bus;\n+\tint ret;\n+\n+\tbus = devm_mdiobus_alloc_size(dev, 0);\n+\tif (!bus)\n+\t\treturn -ENOMEM;\n+\n+\tbus-\u003ename = \"Rockchip DW XPCS MCI/APB3\";\n+\tbus-\u003eread = xpcs_rk_read_c22;\n+\tbus-\u003ewrite = xpcs_rk_write_c22;\n+\tbus-\u003eread_c45 = xpcs_rk_read_c45;\n+\tbus-\u003ewrite_c45 = xpcs_rk_write_c45;\n+\tbus-\u003ephy_mask = ~0;\n+\tbus-\u003eparent = dev;\n+\tbus-\u003epriv = pxpcs;\n+\n+\tsnprintf(bus-\u003eid, MII_BUS_ID_SIZE,\n+\t\t \"rockchip_dwxpcs-%x\", atomic_inc_return(\u0026id));\n+\n+\t/*\n+\t * MDIO-bus here serves as just a back-end engine abstracting out\n+\t * the MDIO and MCI/APB3 IO interfaces utilized for the Rockchip DWXPCS CSRs\n+\t * access.\n+\t */\n+\tret = devm_mdiobus_register(dev, bus);\n+\tif (ret) {\n+\t\tdev_err(dev, \"Failed to create MDIO bus\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tpxpcs-\u003ebus = bus;\n+\treturn 0;\n+}\n+\n+static int xpcs_rk_probe(struct platform_device *pdev)\n+{\n+\tstruct dw_xpcs_rk *pxpcs;\n+\tint ret;\n+\n+\tpxpcs = xpcs_rk_create_data(pdev);\n+\tif (IS_ERR(pxpcs))\n+\t\treturn PTR_ERR(pxpcs);\n+\n+\t/*\n+\t * The XPCS may be attached to a power domain (e.g. PD_PIPE). The domain\n+\t * must be powered on before any register access, otherwise the SoC will\n+\t * trigger a synchronous external abort (SError).\n+\t *\n+\t * Accessing the XPCS registers also requires a TX clock from the SerDes,\n+\t * which is needed for the soft reset.\n+\t */\n+\tret = xpcs_rk_serdes_phy_init(pxpcs);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = xpcs_rk_serdes_phy_poweron(pxpcs);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = xpcs_rk_init_res(pxpcs);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = xpcs_rk_init_clk(pxpcs);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = xpcs_rk_init_bus(pxpcs);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn 0;\n+}\n+\n+static const struct of_device_id xpcs_rk_of_ids[] = {\n+\t{ .compatible = \"rockchip,rk3568-xpcs\" },\n+\t{ /* sentinel */ },\n+};\n+MODULE_DEVICE_TABLE(of, xpcs_rk_of_ids);\n+\n+struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np)\n+{\n+\tstruct platform_device *pdev;\n+\tstruct device_node *pcs_np;\n+\tstruct dw_xpcs_rk *pxpcs;\n+\tstruct dw_xpcs *xpcs;\n+\tu32 port;\n+\n+\tif (!of_device_is_available(np))\n+\t\treturn ERR_PTR(-ENODEV);\n+\n+\tif (of_property_read_u32(np, \"reg\", \u0026port))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tif (!xpcs_rk_mdio_addr_validate((int)port))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\t/* The XPCS pdev is attached to the parent node */\n+\tpcs_np = of_get_parent(np);\n+\tif (!pcs_np)\n+\t\treturn ERR_PTR(-ENODEV);\n+\n+\tif (!of_device_is_available(pcs_np)) {\n+\t\tof_node_put(pcs_np);\n+\t\treturn ERR_PTR(-ENODEV);\n+\t}\n+\n+\tif (!of_match_node(xpcs_rk_of_ids, pcs_np)) {\n+\t\tof_node_put(pcs_np);\n+\t\treturn ERR_PTR(-EINVAL);\n+\t}\n+\n+\tpdev = of_find_device_by_node(pcs_np);\n+\tof_node_put(pcs_np);\n+\tif (!pdev)\n+\t\treturn ERR_PTR(-EPROBE_DEFER);\n+\n+\tif (!device_link_add(dev, \u0026pdev-\u003edev, DL_FLAG_AUTOREMOVE_CONSUMER)) {\n+\t\tput_device(\u0026pdev-\u003edev);\n+\t\treturn ERR_PTR(-ENOMEM);\n+\t}\n+\n+\tpxpcs = platform_get_drvdata(pdev);\n+\tif (!pxpcs || !pxpcs-\u003ebus) {\n+\t\tdevice_link_remove(dev, \u0026pdev-\u003edev);\n+\t\tput_device(\u0026pdev-\u003edev);\n+\t\treturn ERR_PTR(-EPROBE_DEFER);\n+\t}\n+\n+\txpcs = xpcs_create_mdiodev(pxpcs-\u003ebus, (int)port);\n+\tif (IS_ERR(xpcs)) {\n+\t\tdevice_link_remove(dev, \u0026pdev-\u003edev);\n+\t\tput_device(\u0026pdev-\u003edev);\n+\t\treturn xpcs;\n+\t}\n+\n+\txpcs_config_eee_mult_fact(xpcs, pxpcs-\u003eeee_mult_fact);\n+\tput_device(\u0026pdev-\u003edev);\n+\treturn xpcs;\n+}\n+EXPORT_SYMBOL_GPL(xpcs_rk_create);\n+\n+static int xpcs_rk_pm_runtime_suspend(struct device *dev)\n+{\n+\tstruct dw_xpcs_rk *pxpcs = dev_get_drvdata(dev);\n+\n+\tclk_disable_unprepare(pxpcs-\u003ecsr_clk);\n+\n+\treturn 0;\n+}\n+\n+static int xpcs_rk_pm_runtime_resume(struct device *dev)\n+{\n+\tstruct dw_xpcs_rk *pxpcs = dev_get_drvdata(dev);\n+\n+\treturn clk_prepare_enable(pxpcs-\u003ecsr_clk);\n+}\n+\n+static DEFINE_RUNTIME_DEV_PM_OPS(xpcs_rk_pm_ops,\n+\t\t\t   xpcs_rk_pm_runtime_suspend,\n+\t\t\t   xpcs_rk_pm_runtime_resume,\n+\t\t\t   NULL);\n+\n+static struct platform_driver xpcs_rk_driver = {\n+\t.probe = xpcs_rk_probe,\n+\t.driver = {\n+\t\t.name = \"rk_xpcs-dwxpcs\",\n+\t\t.pm = pm_ptr(\u0026xpcs_rk_pm_ops),\n+\t\t.of_match_table = xpcs_rk_of_ids,\n+\t},\n+};\n+module_platform_driver(xpcs_rk_driver);\n+\n+MODULE_DESCRIPTION(\"Rockchip XPCS platform device driver\");\n+MODULE_AUTHOR(\"Coia Prant \u003ccoiaprant@gmail.com\u003e\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c\nindex 0337e2bcc0125..8c3875b6985b9 100644\n--- a/drivers/net/pcs/pcs-xpcs.c\n+++ b/drivers/net/pcs/pcs-xpcs.c\n@@ -761,7 +761,9 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,\n \t *    DW xPCS used with DW EQoS MAC is always MAC side SGMII.\n \t * 4) VR_MII_DIG_CTRL1 Bit(9) [MAC_AUTO_SW] = 1b (Automatic\n \t *    speed/duplex mode change by HW after SGMII AN complete)\n-\t * 5) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 1b (Enable SGMII AN)\n+\t * 5) VR_MII_AN_INTR_STS = 0x0 (Clear CL37 AN complete status)\n+\t * 6) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 1b (Enable SGMII AN)\n+\t *    VR_MII_MMD_CTRL Bit(9) [AN_RESTART] = 1b (Restart SGMII AN)\n \t *\n \t * Note that VR_MII_MMD_CTRL is MII_BMCR.\n \t *\n@@ -769,7 +771,14 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,\n \t *\t SR_MII_AN_ADV. MAC side SGMII receives AN Tx Config from\n \t *\t PHY about the link state change after C28 AN is completed\n \t *\t between PHY and Link Partner. There is also no need to\n-\t *\t trigger AN restart for MAC-side SGMII.\n+\t *\t trigger AN restart for MAC-side SGMII on most devices.\n+\t *\n+\t * Note: While the DesignWare databook states that AN restart is\n+\t *\t not needed for MAC side SGMII, some implementations (e.g.\n+\t *\t Rockchip RK3568) exhibit a timing quirk when integrated with\n+\t *\t phylink and do not restart AN automatically when the link\n+\t *\t comes back up. An explicit AN restart is required on those\n+\t *\t parts to recover the link after a disconnect.\n \t */\n \tmdio_ctrl = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMCR);\n \tif (mdio_ctrl \u003c 0)\n@@ -816,9 +825,14 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,\n \tif (ret \u003c 0)\n \t\treturn ret;\n \n+\t/* Clear CL37 AN complete status */\n+\tret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n \tif (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)\n \t\tret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,\n-\t\t\t\t mdio_ctrl | BMCR_ANENABLE);\n+\t\t\t\t mdio_ctrl | BMCR_ANENABLE | BMCR_ANRESTART);\n \n \treturn ret;\n }\n@@ -1093,9 +1107,18 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs,\n \t\treturn 0;\n \t}\n \n-\t/* Clear AN complete status or interrupt */\n-\tif (state-\u003ean_complete)\n-\t\txpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);\n+\tif (state-\u003ean_complete) {\n+\t\t/* Clear AN complete status or interrupt */\n+\t\tret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\n+\t\t/* Initiate the next round of AN */\n+\t\tret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,\n+\t\t\t\t  BMCR_ANRESTART);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t}\n \n \treturn 0;\n }\ndiff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c\nindex 7843356a4dd47..7b867e7520064 100644\n--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c\n+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c\n@@ -186,6 +186,7 @@ struct rockchip_combphy_grfcfg {\n \tstruct combphy_reg pipe_xpcs_phy_ready;\n \tstruct combphy_reg pipe_pcie1l0_sel;\n \tstruct combphy_reg pipe_pcie1l1_sel;\n+\tstruct combphy_reg pipe_sgmii_mac_sel;\n \tstruct combphy_reg u3otg0_port_en;\n \tstruct combphy_reg u3otg1_port_en;\n };\n@@ -212,6 +213,7 @@ struct rockchip_combphy_priv {\n \tbool enable_ssc;\n \tbool ext_refclk;\n \tstruct clk *refclk;\n+\tu32 sgmii_mac_sel;\n };\n \n static void rockchip_combphy_updatel(struct rockchip_combphy_priv *priv,\n@@ -375,6 +377,9 @@ static int rockchip_combphy_parse_dt(struct device *dev, struct rockchip_combphy\n \n \tpriv-\u003eext_refclk = device_property_present(dev, \"rockchip,ext-refclk\");\n \n+\tpriv-\u003esgmii_mac_sel = 1;\n+\tdevice_property_read_u32(dev, \"rockchip,sgmii-mac-sel\", \u0026priv-\u003esgmii_mac_sel);\n+\n \tpriv-\u003ephy_rst = devm_reset_control_get_exclusive(dev, \"phy\");\n \t/* fallback to old behaviour */\n \tif (PTR_ERR(priv-\u003ephy_rst) == -ENOENT)\n@@ -873,6 +878,8 @@ static int rk3568_combphy_cfg(struct rockchip_combphy_priv *priv)\n \t\tbreak;\n \n \tcase PHY_TYPE_SGMII:\n+\t\trockchip_combphy_param_write(priv-\u003epipe_grf, \u0026cfg-\u003epipe_sgmii_mac_sel,\n+\t\t\t\t\t     priv-\u003esgmii_mac_sel \u003e 0);\n \t\trockchip_combphy_param_write(priv-\u003epipe_grf, \u0026cfg-\u003epipe_xpcs_phy_ready, true);\n \t\trockchip_combphy_param_write(priv-\u003ephy_grf, \u0026cfg-\u003epipe_phymode_sel, true);\n \t\trockchip_combphy_param_write(priv-\u003ephy_grf, \u0026cfg-\u003epipe_sel_qsgmii, true);\n@@ -984,6 +991,7 @@ static const struct rockchip_combphy_grfcfg rk3568_combphy_grfcfgs = {\n \t.con3_for_sata\t\t= { 0x000c, 15, 0, 0x00, 0x4407 },\n \t/* pipe-grf */\n \t.pipe_con0_for_sata\t= { 0x0000, 15, 0, 0x00, 0x2220 },\n+\t.pipe_sgmii_mac_sel\t= { 0x0040, 1, 1, 0x00, 0x01 },\n \t.pipe_xpcs_phy_ready\t= { 0x0040, 2, 2, 0x00, 0x01 },\n \t.u3otg0_port_en\t\t= { 0x0104, 15, 0, 0x0181, 0x1100 },\n \t.u3otg1_port_en\t\t= { 0x0144, 15, 0, 0x0181, 0x1100 },\ndiff --git a/include/linux/pcs/pcs-xpcs-rk.h b/include/linux/pcs/pcs-xpcs-rk.h\nnew file mode 100644\nindex 0000000000000..28723d5bd75cc\n--- /dev/null\n+++ b/include/linux/pcs/pcs-xpcs-rk.h\n@@ -0,0 +1,11 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+#ifndef __LINUX_PCS_XPCS_ROCKCHIP_H\n+#define __LINUX_PCS_XPCS_ROCKCHIP_H\n+\n+#include \u003clinux/device.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/pcs/pcs-xpcs.h\u003e\n+\n+struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np);\n+\n+#endif /* __LINUX_PCS_XPCS_ROCKCHIP_H */\n"
}

2/1 2026/09/15 16:25 agent patch-evaluator
2m Model:
core

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces support for the Rockchip RK3568 DesignWare Ethernet PCS (XPCS) and refactors PCS initialization in the STMMAC Ethernet driver family (dwmac-rk, dwmac-intel, dwmac-renesas, dwmac-socfpga, dwmac-rzn1). All modified code is structurally unreachable on amd64 in standard virtualized fuzzing environments (QEMU/GCE): documentation/device tree bindings and arm64 DTS changes are non-executable on amd64, and the affected drivers (stmmac dwmac variants and Synopsys DW XPCS) require physical SoC platform devices or physical Intel/Rockchip/Renesas silicon not emulated by QEMU.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 011fc215fabd5fa95179cc38bd0ba811e60f999c
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Sep 15 16:25:35 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml b/Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml
new file mode 100644
index 0000000000000..703fcff0e3f70
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml
@@ -0,0 +1,110 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/pcs/rockchip,rk3568-xpcs.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip RK3568 Synopsys DesignWare Ethernet PCS
+
+maintainers:
+  - Coia Prant <coiaprant@gmail.com>
+
+description: |
+  Rockchip RK3568 SoC integrates a Synopsys DesignWare Ethernet Physical
+  Coding Sublayer (XPCS).
+  The PCS provides an interface between the Media Access Control (MAC)
+  and the Physical Medium Attachment (PMA) sublayer through a Media
+  Independent Interface (GMII).
+
+  The XPCS is accessed over the APB3 bus and internally connected to a
+  Naneng Combo SerDes PHY.
+  It supports 1000BASE-X, SGMII and QSGMII modes.
+
+  The block contains four MII ports that can be individually enabled and
+  routed to one of the Ethernet GMAC controllers via the pcs-handle
+  property in the MAC device tree node.
+
+properties:
+  compatible:
+    const: rockchip,rk3568-xpcs
+
+  reg:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  clocks:
+    items:
+      - description: APB3 bus interface clock (clk_csr_i), required for register access
+      - description: EEE clock (clk_eee_i), required for Energy Efficient Ethernet operation
+
+  clock-names:
+    items:
+      - const: csr
+      - const: eee
+
+  phys:
+    maxItems: 1
+
+  phy-names:
+    const: serdes
+
+  power-domains:
+    maxItems: 1
+
+patternProperties:
+  "^ethernet-pcs-mii@[0-3]$":
+    type: object
+    description:
+      One of the four MII ports of the XPCS. The port is linked to an
+      Ethernet MAC controller via the pcs-handle property in the MAC's
+      device tree node.
+
+    properties:
+      reg:
+        description: MII port number.
+        enum: [0, 1, 2, 3]
+
+    required:
+      - reg
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - "#address-cells"
+  - "#size-cells"
+  - clocks
+  - clock-names
+  - phys
+  - phy-names
+  - power-domains
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/rk3568-cru.h>
+    #include <dt-bindings/power/rk3568-power.h>
+    #include <dt-bindings/phy/phy.h>
+
+    ethernet-pcs@fda00000 {
+      compatible = "rockchip,rk3568-xpcs";
+      reg = <0xfda00000 0x200000>;
+      #address-cells = <1>;
+      #size-cells = <0>;
+      clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
+      clock-names = "csr", "eee";
+      phys = <&combphy2 PHY_TYPE_SGMII>;
+      phy-names = "serdes";
+      power-domains = <&power RK3568_PD_PIPE>;
+
+      ethernet-pcs-mii@0 {
+        reg = <0>;
+      };
+    };
diff --git a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
index 80c252845349c..67b21ea77ec96 100644
--- a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
+++ b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
@@ -120,6 +120,12 @@ properties:
     maximum: 0x7F
     default: 0x10
 
+  pcs-handle:
+    description:
+      Specifies a reference to a node representing the PCS device
+      connected to this GMAC. Required when phy-mode is "sgmii".
+    maxItems: 1
+
   phy-supply:
     description: PHY regulator
 
@@ -159,6 +165,15 @@ allOf:
         clocks:
           minItems: 5
 
+  - if:
+      properties:
+        phy-mode:
+          contains:
+            const: sgmii
+    then:
+      required:
+        - pcs-handle
+
 unevaluatedProperties: false
 
 examples:
diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml b/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml
index 379b08bd9e97a..8e898bce9af73 100644
--- a/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml
+++ b/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml
@@ -80,6 +80,15 @@ properties:
     description:
       Some additional pipe settings are accessed through GRF regs.
 
+  rockchip,sgmii-mac-sel:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    enum: [0, 1]
+    default: 1
+    description:
+      Select gmac0 or gmac1 to be used as SGMII controller.
+      The hardware reset value is GMAC1 (1). Set this to 0 to route
+      SGMII to GMAC0.
+
   "#phy-cells":
     const: 1
 
@@ -105,6 +114,10 @@ allOf:
           maxItems: 1
         reset-names:
           maxItems: 1
+        rockchip,sgmii-mac-sel: true
+    else:
+      properties:
+        rockchip,sgmii-mac-sel: false
   - if:
       properties:
         compatible:
diff --git a/MAINTAINERS b/MAINTAINERS
index 3b2eb2a7a89a8..f53a519da6298 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23739,6 +23739,15 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/sound/rockchip,rk3576-sai.yaml
 F:	sound/soc/rockchip/rockchip_sai.*
 
+ROCKCHIP XPCS DRIVER
+M:	Coia Prant <coiaprant@gmail.com>
+L:	netdev@vger.kernel.org
+L:	linux-rockchip@lists.infradead.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml
+F:	drivers/net/pcs/pcs-xpcs-rk.c
+F:	include/linux/pcs/pcs-xpcs-rk.h
+
 ROCKER DRIVER
 M:	Jiri Pirko <jiri@resnulli.us>
 L:	netdev@vger.kernel.org
diff --git a/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts b/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
index 58c1052ba8ef3..fdaa4a2a4328b 100644
--- a/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
@@ -3,6 +3,7 @@
 /dts-v1/;
 
 #include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/leds/common.h>
 #include <dt-bindings/pinctrl/rockchip.h>
 #include <dt-bindings/soc/rockchip,vop2.h>
 #include "rk3568.dtsi"
@@ -241,6 +242,7 @@ &combphy1 {
 };
 
 &combphy2 {
+	rockchip,sgmii-mac-sel = <0>;
 	status = "okay";
 };
 
@@ -260,9 +262,18 @@ &cpu3 {
 	cpu-supply = <&vdd_cpu>;
 };
 
-/* Motorcomm YT8521SC LAN port (require SGMII) */
+/* Motorcomm YT8521SC LAN port */
 &gmac0 {
-	status = "disabled";
+	assigned-clocks = <&cru SCLK_GMAC0_RX_TX>;
+	assigned-clock-parents = <&clk_gmac0_xpcs_mii>;
+	managed = "in-band-status";
+	pcs-handle = <&xpcs_mii0>;
+	phy-handle = <&sgmii_phy>;
+	phy-mode = "sgmii";
+	phy-supply = <&vcc_3v3>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&gmac0_miim>;
+	status = "okay";
 };
 
 /* Motorcomm YT8521SC WAN port */
@@ -341,6 +352,36 @@ &i2s0_8ch {
 	status = "okay";
 };
 
+&mdio0 {
+	sgmii_phy: ethernet-phy@3 {
+		compatible = "ethernet-phy-ieee802.3-c22";
+		reg = <0x3>;
+		max-speed = <1000>;
+		reset-assert-us = <20000>;
+		reset-deassert-us = <100000>;
+		reset-gpios = <&gpio3 RK_PC6 GPIO_ACTIVE_LOW>;
+
+		leds {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			led@1 {
+				reg = <1>;
+				color = <LED_COLOR_ID_AMBER>;
+				function = LED_FUNCTION_LAN;
+				default-state = "keep";
+			};
+
+			led@2 {
+				reg = <2>;
+				color = <LED_COLOR_ID_GREEN>;
+				function = LED_FUNCTION_LAN;
+				default-state = "keep";
+			};
+		};
+	};
+};
+
 &mdio1 {
 	rgmii_phy: ethernet-phy@3 {
 		compatible = "ethernet-phy-ieee802.3-c22";
@@ -350,6 +391,25 @@ rgmii_phy: ethernet-phy@3 {
 		reset-gpios = <&gpio4 RK_PC0 GPIO_ACTIVE_LOW>;
 		rx-internal-delay-ps = <1500>;
 		tx-internal-delay-ps = <1500>;
+
+		leds {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			led@1 {
+				reg = <1>;
+				color = <LED_COLOR_ID_AMBER>;
+				function = LED_FUNCTION_WAN;
+				default-state = "keep";
+			};
+
+			led@2 {
+				reg = <2>;
+				color = <LED_COLOR_ID_GREEN>;
+				function = LED_FUNCTION_WAN;
+				default-state = "keep";
+			};
+		};
 	};
 };
 
@@ -586,3 +646,13 @@ &xin32k {
 	pinctrl-names = "default";
 	pinctrl-0 = <&clk32k_out1>;
 };
+
+&xpcs {
+	phys = <&combphy2 PHY_TYPE_SGMII>;
+	phy-names = "serdes";
+	status = "okay";
+};
+
+&xpcs_mii0 {
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/rockchip/rk3568.dtsi b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
index 3bc653f027f1f..227d03e336043 100644
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
@@ -110,6 +110,51 @@ sata0: sata@fc000000 {
 		status = "disabled";
 	};
 
+	xpcs: ethernet-pcs@fda00000 {
+		compatible = "rockchip,rk3568-xpcs";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		reg = <0x0 0xfda00000 0x0 0x200000>;
+		clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
+		clock-names = "csr", "eee";
+		power-domains = <&power RK3568_PD_PIPE>;
+		status = "disabled";
+
+		xpcs_mii0: ethernet-pcs-mii@0 {
+			reg = <0>;
+			status = "disabled";
+		};
+
+		xpcs_mii1: ethernet-pcs-mii@1 {
+			reg = <1>;
+			status = "disabled";
+		};
+
+		xpcs_mii2: ethernet-pcs-mii@2 {
+			reg = <2>;
+			status = "disabled";
+		};
+
+		xpcs_mii3: ethernet-pcs-mii@3 {
+			reg = <3>;
+			status = "disabled";
+		};
+	};
+
+	clk_gmac0_xpcs_mii: clock-xpcs-gmac0 {
+		compatible = "fixed-clock";
+		clock-frequency = <125000000>;
+		clock-output-names = "clk_gmac0_xpcs_mii";
+		#clock-cells = <0>;
+	};
+
+	clk_gmac1_xpcs_mii: clock-xpcs-gmac1 {
+		compatible = "fixed-clock";
+		clock-frequency = <125000000>;
+		clock-output-names = "clk_gmac1_xpcs_mii";
+		#clock-cells = <0>;
+	};
+
 	pipe_phy_grf0: syscon@fdc70000 {
 		compatible = "rockchip,rk3568-pipe-phy-grf", "syscon";
 		reg = <0x0 0xfdc70000 0x0 0x1000>;
diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index e3dd5adda5aca..5088acc06982e 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -170,6 +170,7 @@ config DWMAC_ROCKCHIP
 	default ARCH_ROCKCHIP
 	depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
 	select MFD_SYSCON
+	select PCS_XPCS_ROCKCHIP
 	help
 	  Support for Ethernet controller on Rockchip RK3288 SoC.
 
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
index f5f9fa67ecd77..4308dccbf2570 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
@@ -603,13 +603,47 @@ static void common_default_data(struct plat_stmmacenet_data *plat)
 	plat->mdio_bus_data->needs_reset = true;
 }
 
+static int intel_mgbe_pcs_init(struct stmmac_priv *priv)
+{
+	struct fwnode_handle *devnode, *pcsnode;
+	struct dw_xpcs *xpcs;
+	int addr;
+
+	devnode = dev_fwnode(priv->device);
+
+	if (fwnode_property_present(devnode, "pcs-handle")) {
+		pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
+		xpcs = xpcs_create_fwnode(pcsnode);
+		fwnode_handle_put(pcsnode);
+	} else {
+		addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
+		xpcs = xpcs_create_mdiodev(priv->mii, addr);
+	}
+
+	if (IS_ERR(xpcs))
+		return PTR_ERR(xpcs);
+
+	xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
+
+	priv->hw->xpcs = xpcs;
+	return 0;
+}
+
+static void intel_mgbe_pcs_exit(struct stmmac_priv *priv)
+{
+	if (!priv->hw->xpcs)
+		return;
+
+	xpcs_destroy(priv->hw->xpcs);
+	priv->hw->xpcs = NULL;
+}
+
 static struct phylink_pcs *intel_mgbe_select_pcs(struct stmmac_priv *priv,
 						 phy_interface_t interface)
 {
-	/* plat->mdio_bus_data->has_xpcs has been set true, so there
-	 * should always be an XPCS. The original code would always
-	 * return this if present.
-	 */
+	if (!priv->hw->xpcs)
+		return NULL;
+
 	return xpcs_to_phylink_pcs(priv->hw->xpcs);
 }
 
@@ -733,6 +767,8 @@ static int intel_mgbe_common_data(struct pci_dev *pdev,
 	    plat->phy_interface == PHY_INTERFACE_MODE_1000BASEX) {
 		plat->mdio_bus_data->pcs_mask = BIT_U32(INTEL_MGBE_XPCS_ADDR);
 		plat->default_an_inband = true;
+		plat->pcs_init = intel_mgbe_pcs_init;
+		plat->pcs_exit = intel_mgbe_pcs_exit;
 		plat->select_pcs = intel_mgbe_select_pcs;
 	}
 
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
index 19f34e18bfef2..9af32c26f9c14 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
@@ -81,8 +81,11 @@ static int renesas_gmac_pcs_init(struct stmmac_priv *priv)
 
 static void renesas_gmac_pcs_exit(struct stmmac_priv *priv)
 {
-	if (priv->hw->phylink_pcs)
-		miic_destroy(priv->hw->phylink_pcs);
+	if (!priv->hw->phylink_pcs)
+		return;
+
+	miic_destroy(priv->hw->phylink_pcs);
+	priv->hw->phylink_pcs = NULL;
 }
 
 static struct phylink_pcs *renesas_gmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
index 8d7042e689261..b3d746dd966ff 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
@@ -20,6 +20,7 @@
 #include <linux/delay.h>
 #include <linux/mfd/syscon.h>
 #include <linux/regmap.h>
+#include <linux/pcs/pcs-xpcs-rk.h>
 #include <linux/pm_runtime.h>
 
 #include "stmmac_platform.h"
@@ -47,6 +48,7 @@ struct rk_gmac_ops {
 	void (*set_to_rgmii)(struct rk_priv_data *bsp_priv,
 			     int tx_delay, int rx_delay);
 	void (*set_to_rmii)(struct rk_priv_data *bsp_priv);
+	void (*set_to_sgmii)(struct rk_priv_data *bsp_priv);
 	int (*set_speed)(struct rk_priv_data *bsp_priv,
 			 phy_interface_t interface, int speed);
 	void (*integrated_phy_powerup)(struct rk_priv_data *bsp_priv);
@@ -63,6 +65,7 @@ struct rk_gmac_ops {
 	bool clock_grf_reg_in_php;
 	bool supports_rgmii;
 	bool supports_rmii;
+	bool supports_sgmii;
 	bool php_grf_required;
 	bool regs_valid;
 	u32 regs[];
@@ -98,6 +101,7 @@ struct rk_priv_data {
 	bool integrated_phy;
 	bool supports_rgmii;
 	bool supports_rmii;
+	bool supports_sgmii;
 
 	struct clk_bulk_data *clks;
 	int num_clks;
@@ -809,6 +813,8 @@ static const struct rk_gmac_ops rk3528_ops = {
 #define RK3568_GRF_GMAC1_CON1		0x038c
 
 /* RK3568_GRF_GMAC0_CON1 && RK3568_GRF_GMAC1_CON1 */
+#define RK3568_GMAC_MODE_RMII_RGMII		GRF_CLR_BIT(7)
+#define RK3568_GMAC_MODE_SGMII_QSGMII		GRF_BIT(7)
 #define RK3568_GMAC_FLOW_CTRL			GRF_BIT(3)
 #define RK3568_GMAC_FLOW_CTRL_CLR		GRF_CLR_BIT(3)
 #define RK3568_GMAC_RXCLK_DLY_ENABLE		GRF_BIT(1)
@@ -836,6 +842,16 @@ static int rk3568_init(struct rk_priv_data *bsp_priv)
 	}
 }
 
+static void rk3568_set_to_rmii(struct rk_priv_data *bsp_priv)
+{
+	u32 con1;
+
+	con1 = (bsp_priv->id == 1) ? RK3568_GRF_GMAC1_CON1 :
+				     RK3568_GRF_GMAC0_CON1;
+
+	regmap_write(bsp_priv->grf, con1, RK3568_GMAC_MODE_RMII_RGMII);
+}
+
 static void rk3568_set_to_rgmii(struct rk_priv_data *bsp_priv,
 				int tx_delay, int rx_delay)
 {
@@ -851,19 +867,31 @@ static void rk3568_set_to_rgmii(struct rk_priv_data *bsp_priv,
 		     RK3568_GMAC_CLK_TX_DL_CFG(tx_delay));
 
 	regmap_write(bsp_priv->grf, con1,
+		     RK3568_GMAC_MODE_RMII_RGMII |
 		     RK3568_GMAC_RXCLK_DLY_ENABLE |
 		     RK3568_GMAC_TXCLK_DLY_ENABLE);
 }
 
+static void rk3568_set_to_sgmii(struct rk_priv_data *bsp_priv)
+{
+	u32 con1;
+
+	con1 = (bsp_priv->id == 1) ? RK3568_GRF_GMAC1_CON1 :
+				     RK3568_GRF_GMAC0_CON1;
+
+	regmap_write(bsp_priv->grf, con1, RK3568_GMAC_MODE_SGMII_QSGMII);
+}
+
 static const struct rk_gmac_ops rk3568_ops = {
 	.init = rk3568_init,
+	.set_to_rmii = rk3568_set_to_rmii,
 	.set_to_rgmii = rk3568_set_to_rgmii,
+	.set_to_sgmii = rk3568_set_to_sgmii,
+
 	.set_speed = rk_set_clk_mac_speed,
 
 	.gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),
 
-	.supports_rmii = true,
-
 	.regs_valid = true,
 	.regs = {
 		0xfe2a0000, /* gmac0 */
@@ -1208,6 +1236,43 @@ static void rk_phy_powerdown(struct rk_priv_data *bsp_priv)
 		dev_err(bsp_priv->dev, "fail to disable phy-supply\n");
 }
 
+static int rk_pcs_init(struct stmmac_priv *priv)
+{
+	struct device_node *np = priv->device->of_node;
+	struct device_node *pcs_node;
+	struct dw_xpcs *xpcs;
+
+	pcs_node = of_parse_phandle(np, "pcs-handle", 0);
+	if (!pcs_node)
+		return -ENODEV;
+
+	xpcs = xpcs_rk_create(priv->device, pcs_node);
+	of_node_put(pcs_node);
+	if (IS_ERR(xpcs))
+		return PTR_ERR(xpcs);
+
+	priv->hw->xpcs = xpcs;
+	return 0;
+}
+
+static void rk_pcs_exit(struct stmmac_priv *priv)
+{
+	if (!priv->hw->xpcs)
+		return;
+
+	xpcs_destroy(priv->hw->xpcs);
+	priv->hw->xpcs = NULL;
+}
+
+static struct phylink_pcs *rk_select_pcs(struct stmmac_priv *priv,
+					 phy_interface_t interface)
+{
+	if (!priv->hw->xpcs)
+		return NULL;
+
+	return xpcs_to_phylink_pcs(priv->hw->xpcs);
+}
+
 static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev,
 					  struct plat_stmmacenet_data *plat,
 					  const struct rk_gmac_ops *ops)
@@ -1330,6 +1395,7 @@ static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev,
 
 	bsp_priv->supports_rgmii = ops->supports_rgmii || !!ops->set_to_rgmii;
 	bsp_priv->supports_rmii = ops->supports_rmii || !!ops->set_to_rmii;
+	bsp_priv->supports_sgmii = ops->supports_sgmii || !!ops->set_to_sgmii;
 
 	if (ops->init) {
 		ret = ops->init(bsp_priv);
@@ -1361,6 +1427,10 @@ static int rk_gmac_check_ops(struct rk_priv_data *bsp_priv)
 		if (!bsp_priv->supports_rmii)
 			return -EINVAL;
 		break;
+	case PHY_INTERFACE_MODE_SGMII:
+		if (!bsp_priv->supports_sgmii)
+			return -EINVAL;
+		break;
 	default:
 		dev_err(bsp_priv->dev,
 			"unsupported interface %d", bsp_priv->phy_iface);
@@ -1379,16 +1449,19 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
 	if (ret)
 		return ret;
 
+	ret = gmac_clk_enable(bsp_priv, true);
+	if (ret)
+		return ret;
+
+	if (bsp_priv->phy_iface == PHY_INTERFACE_MODE_SGMII)
+		goto set_mode;
+
 	ret = rk_get_phy_intf_sel(bsp_priv->phy_iface);
 	if (ret < 0)
-		return ret;
+		goto clk_disable;
 
 	intf = ret;
 
-	ret = gmac_clk_enable(bsp_priv, true);
-	if (ret)
-		return ret;
-
 	if (bsp_priv->gmac_phy_intf_sel_mask ||
 	    bsp_priv->gmac_rmii_mode_mask) {
 		/* If defined, encode the phy_intf_sel value */
@@ -1399,10 +1472,8 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
 				      bsp_priv->gmac_rmii_mode_mask);
 
 		ret = rk_write_gmac_grf_reg(bsp_priv, val);
-		if (ret < 0) {
-			gmac_clk_enable(bsp_priv, false);
-			return ret;
-		}
+		if (ret < 0)
+			goto clk_disable;
 	}
 
 	if (bsp_priv->clock.rmii_mode_mask) {
@@ -1410,13 +1481,12 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
 				     bsp_priv->clock.rmii_mode_mask);
 
 		ret = rk_write_clock_grf_reg(bsp_priv, val);
-		if (ret < 0) {
-			gmac_clk_enable(bsp_priv, false);
-			return ret;
-		}
+		if (ret < 0)
+			goto clk_disable;
 	}
 
-	/*rmii or rgmii*/
+set_mode:
+	/* rmii, rgmii, sgmii */
 	switch (bsp_priv->phy_iface) {
 	case PHY_INTERFACE_MODE_RGMII:
 		dev_info(dev, "init for RGMII\n");
@@ -1447,15 +1517,20 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
 		if (bsp_priv->ops->set_to_rmii)
 			bsp_priv->ops->set_to_rmii(bsp_priv);
 		break;
+	case PHY_INTERFACE_MODE_SGMII:
+		dev_info(dev, "init for SGMII\n");
+		if (bsp_priv->ops->set_to_sgmii)
+			bsp_priv->ops->set_to_sgmii(bsp_priv);
+		break;
 	default:
 		dev_err(dev, "NO interface defined!\n");
+		ret = -EINVAL;
+		goto clk_disable;
 	}
 
 	ret = rk_phy_powerup(bsp_priv);
-	if (ret) {
-		gmac_clk_enable(bsp_priv, false);
-		return ret;
-	}
+	if (ret)
+		goto clk_disable;
 
 	pm_runtime_get_sync(dev);
 
@@ -1463,6 +1538,10 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
 		bsp_priv->ops->integrated_phy_powerup(bsp_priv);
 
 	return 0;
+
+clk_disable:
+	gmac_clk_enable(bsp_priv, false);
+	return ret;
 }
 
 static void rk_gmac_powerdown(struct rk_priv_data *gmac)
@@ -1486,6 +1565,9 @@ static void rk_get_interfaces(struct stmmac_priv *priv, void *bsp_priv,
 
 	if (rk->supports_rmii)
 		__set_bit(PHY_INTERFACE_MODE_RMII, interfaces);
+
+	if (rk->supports_sgmii)
+		__set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);
 }
 
 static int rk_set_clk_tx_rate(void *bsp_priv_, struct clk *clk_tx_i,
@@ -1602,6 +1684,17 @@ static int rk_gmac_probe(struct platform_device *pdev)
 	plat_dat->suspend = rk_gmac_suspend;
 	plat_dat->resume = rk_gmac_resume;
 
+	if (plat_dat->phy_interface == PHY_INTERFACE_MODE_SGMII) {
+		/* SGMII clock always runs at 125 MHz */
+		plat_dat->set_clk_tx_rate = NULL;
+
+		/* SGMII requires a PCS */
+		plat_dat->default_an_inband = true;
+		plat_dat->pcs_init = rk_pcs_init;
+		plat_dat->pcs_exit = rk_pcs_exit;
+		plat_dat->select_pcs = rk_select_pcs;
+	}
+
 	plat_dat->bsp_priv = rk_gmac_setup(pdev, plat_dat, data);
 	if (IS_ERR(plat_dat->bsp_priv))
 		return PTR_ERR(plat_dat->bsp_priv);
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
index 13634965bc19a..01df4776edb3f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
@@ -35,8 +35,11 @@ static int rzn1_dwmac_pcs_init(struct stmmac_priv *priv)
 
 static void rzn1_dwmac_pcs_exit(struct stmmac_priv *priv)
 {
-	if (priv->hw->phylink_pcs)
-		miic_destroy(priv->hw->phylink_pcs);
+	if (!priv->hw->phylink_pcs)
+		return;
+
+	miic_destroy(priv->hw->phylink_pcs);
+	priv->hw->phylink_pcs = NULL;
 }
 
 static struct phylink_pcs *rzn1_dwmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
index 1d7f0a57d2889..6d4bc1fe8f751 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
@@ -539,8 +539,11 @@ static int socfpga_dwmac_pcs_init(struct stmmac_priv *priv)
 
 static void socfpga_dwmac_pcs_exit(struct stmmac_priv *priv)
 {
-	if (priv->hw->phylink_pcs)
-		lynx_pcs_destroy(priv->hw->phylink_pcs);
+	if (!priv->hw->phylink_pcs)
+		return;
+
+	lynx_pcs_destroy(priv->hw->phylink_pcs);
+	priv->hw->phylink_pcs = NULL;
 }
 
 static struct phylink_pcs *socfpga_dwmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
index afe98ff5bdcb0..7396b68899c66 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
@@ -426,35 +426,14 @@ int stmmac_mdio_reset(struct mii_bus *bus)
 int stmmac_pcs_setup(struct net_device *ndev)
 {
 	struct stmmac_priv *priv = netdev_priv(ndev);
-	struct fwnode_handle *devnode, *pcsnode;
-	struct dw_xpcs *xpcs = NULL;
-	int addr, ret;
-
-	devnode = dev_fwnode(priv->device);
-
-	if (priv->plat->pcs_init) {
-		ret = priv->plat->pcs_init(priv);
-	} else if (fwnode_property_present(devnode, "pcs-handle")) {
-		pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
-		xpcs = xpcs_create_fwnode(pcsnode);
-		fwnode_handle_put(pcsnode);
-		ret = PTR_ERR_OR_ZERO(xpcs);
-	} else if (priv->plat->mdio_bus_data &&
-		   priv->plat->mdio_bus_data->pcs_mask) {
-		addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
-		xpcs = xpcs_create_mdiodev(priv->mii, addr);
-		ret = PTR_ERR_OR_ZERO(xpcs);
-	} else {
+	int ret;
+
+	if (!priv->plat->pcs_init)
 		return 0;
-	}
 
+	ret = priv->plat->pcs_init(priv);
 	if (ret)
-		return dev_err_probe(priv->device, ret, "No xPCS found\n");
-
-	if (xpcs)
-		xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
-
-	priv->hw->xpcs = xpcs;
+		return dev_err_probe(priv->device, ret, "Failed to initialize PCS\n");
 
 	return 0;
 }
@@ -463,14 +442,10 @@ void stmmac_pcs_clean(struct net_device *ndev)
 {
 	struct stmmac_priv *priv = netdev_priv(ndev);
 
-	if (priv->plat->pcs_exit)
-		priv->plat->pcs_exit(priv);
-
-	if (!priv->hw->xpcs)
+	if (!priv->plat->pcs_exit)
 		return;
 
-	xpcs_destroy(priv->hw->xpcs);
-	priv->hw->xpcs = NULL;
+	priv->plat->pcs_exit(priv);
 }
 
 struct stmmac_clk_rate {
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
index e417fd66f660a..3db9b343ac0c7 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -12,6 +12,31 @@ config PCS_XPCS
 	  This module provides a driver and helper functions for Synopsys
 	  DesignWare XPCS controllers.
 
+if PCS_XPCS
+
+config PCS_XPCS_PLATFORM
+	tristate "Generic XPCS controller support"
+	default PCS_XPCS
+	help
+	  Generic DWXPCS driver for platforms that don't require any
+	  platform specific code to function or is using platform
+	  data for setup.
+
+	  If you have a controller with this interface, say Y or M here.
+
+config PCS_XPCS_ROCKCHIP
+	tristate "Rockchip XPCS controller support"
+	default ARCH_ROCKCHIP
+	depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
+	select GENERIC_PHY
+	select PM_GENERIC_DOMAINS
+	help
+	  Support for XPCS controller on Rockchip RK356x SoC.
+
+	  If you have a Rockchip SoC with this interface, say Y or M here.
+
+endif # PCS_XPCS
+
 config PCS_LYNX
 	tristate
 	help
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
index 4f7920618b900..f9f6cf2578d72 100644
--- a/drivers/net/pcs/Makefile
+++ b/drivers/net/pcs/Makefile
@@ -1,10 +1,11 @@
 # SPDX-License-Identifier: GPL-2.0
 # Makefile for Linux PCS drivers
 
-pcs_xpcs-$(CONFIG_PCS_XPCS)	:= pcs-xpcs.o pcs-xpcs-plat.o \
-				   pcs-xpcs-nxp.o pcs-xpcs-wx.o
+pcs_xpcs-$(CONFIG_PCS_XPCS)	:= pcs-xpcs.o pcs-xpcs-nxp.o pcs-xpcs-wx.o
 
 obj-$(CONFIG_PCS_XPCS)		+= pcs_xpcs.o
+obj-$(CONFIG_PCS_XPCS_PLATFORM) += pcs-xpcs-plat.o
+obj-$(CONFIG_PCS_XPCS_ROCKCHIP) += pcs-xpcs-rk.o
 obj-$(CONFIG_PCS_LYNX)		+= pcs-lynx.o
 obj-$(CONFIG_PCS_MTK_LYNXI)	+= pcs-mtk-lynxi.o
 obj-$(CONFIG_PCS_RZN1_MIIC)	+= pcs-rzn1-miic.o
diff --git a/drivers/net/pcs/pcs-xpcs-rk.c b/drivers/net/pcs/pcs-xpcs-rk.c
new file mode 100644
index 0000000000000..d5e0f22c973c2
--- /dev/null
+++ b/drivers/net/pcs/pcs-xpcs-rk.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Rockchip XPCS platform device driver
+ *
+ * Based on the Synopsys DesignWare XPCS platform driver.
+ * Copyright (C) 2024 Serge Semin
+ *
+ * Adapted for Rockchip SoCs, with reference to the Rockchip OEM driver.
+ * Copyright (C) 2026 Coia Prant
+ */
+
+#include <linux/atomic.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/math.h>
+#include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/pcs/pcs-xpcs-rk.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/sizes.h>
+#include <linux/time.h>
+
+#include "pcs-xpcs.h"
+
+struct dw_xpcs_rk {
+	struct platform_device *pdev;
+	struct mii_bus *bus;
+	void __iomem *reg_base;
+	struct phy *serdes_phy;
+	struct clk *csr_clk;
+	struct clk *eee_clk;
+	u8 eee_mult_fact;
+};
+
+static ptrdiff_t xpcs_rk_addr_format(int dev, int reg)
+{
+	return FIELD_PREP(0x70000, dev) | FIELD_PREP(0xffff, reg);
+}
+
+static int xpcs_rk_read_reg(struct dw_xpcs_rk *pxpcs, int dev, int reg)
+{
+	ptrdiff_t csr;
+	int ret;
+
+	csr = xpcs_rk_addr_format(dev, reg);
+
+	ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
+	if (ret)
+		return ret;
+
+	ret = readl(pxpcs->reg_base + (csr << 2)) & 0xffff;
+
+	pm_runtime_put(&pxpcs->pdev->dev);
+	return ret;
+}
+
+static int xpcs_rk_write_reg(struct dw_xpcs_rk *pxpcs, int dev, int reg, u16 val)
+{
+	ptrdiff_t csr;
+	int ret;
+
+	csr = xpcs_rk_addr_format(dev, reg);
+
+	ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
+	if (ret)
+		return ret;
+
+	writel(val, pxpcs->reg_base + (csr << 2));
+
+	pm_runtime_put(&pxpcs->pdev->dev);
+	return 0;
+}
+
+#define ROCKCHIP_MMD_MII1	2
+#define ROCKCHIP_MMD_MII2	3
+#define ROCKCHIP_MMD_MII3	4
+#define ROCKCHIP_MMD_PMAPMD	6
+#define ROCKCHIP_MMD_MII	7
+
+static bool xpcs_rk_mdio_addr_validate(int addr)
+{
+	return !(addr < 0 || addr > 3);
+}
+
+static int xpcs_rk_mdio_read_remapping(int addr, int dev, int reg)
+{
+	switch (dev) {
+	case MDIO_MMD_PMAPMD:
+		return ROCKCHIP_MMD_PMAPMD;
+	case MDIO_MMD_VEND2:
+		break;
+	default:
+		return -ENXIO;
+	}
+
+	/*
+	 * Reads are redirected by hardware to the port's read-only mirror;
+	 * only writes have to be targeted at MII (see the write path).
+	 */
+	switch (addr) {
+	case 0:
+		return ROCKCHIP_MMD_MII;
+	case 1:
+		return ROCKCHIP_MMD_MII1;
+	case 2:
+		return ROCKCHIP_MMD_MII2;
+	case 3:
+		return ROCKCHIP_MMD_MII3;
+	default:
+		return -ENODEV;
+	}
+}
+
+static int xpcs_rk_mdio_write_remapping(int addr, int dev, int reg)
+{
+	switch (dev) {
+	case MDIO_MMD_PMAPMD:
+		return ROCKCHIP_MMD_PMAPMD;
+	case MDIO_MMD_VEND2:
+		break;
+	default:
+		return -ENXIO;
+	}
+
+	/*
+	 * These registers physically live only in MII (the management port).
+	 * Ports 1-3 expose read-only mirrors of these bits, so writes must
+	 * always target MII; the read path remaps per address and the
+	 * hardware redirects to the port's mirror.
+	 */
+	switch (reg) {
+	case DW_VR_MII_AN_CTRL:
+	case DW_VR_MII_AN_INTR_STS:
+	case DW_VR_MII_EEE_MCTRL0:
+	case DW_VR_MII_EEE_MCTRL1:
+	case DW_VR_MII_DIG_CTRL2:
+		return ROCKCHIP_MMD_MII;
+	default:
+		break;
+	}
+
+	switch (addr) {
+	case 0:
+		return ROCKCHIP_MMD_MII;
+	case 1:
+		return ROCKCHIP_MMD_MII1;
+	case 2:
+		return ROCKCHIP_MMD_MII2;
+	case 3:
+		return ROCKCHIP_MMD_MII3;
+	default:
+		return -ENODEV;
+	}
+}
+
+static int xpcs_rk_read_c22(struct mii_bus *bus, int addr, int reg)
+{
+	struct dw_xpcs_rk *pxpcs = bus->priv;
+	int dev;
+
+	if (!xpcs_rk_mdio_addr_validate(addr))
+		return -ENODEV;
+
+	dev = xpcs_rk_mdio_read_remapping(addr, MDIO_MMD_VEND2, reg);
+	if (dev < 0)
+		return 0xffff;
+
+	return xpcs_rk_read_reg(pxpcs, dev, reg);
+}
+
+static int xpcs_rk_write_c22(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+	struct dw_xpcs_rk *pxpcs = bus->priv;
+	int dev;
+
+	if (!xpcs_rk_mdio_addr_validate(addr))
+		return -ENODEV;
+
+	dev = xpcs_rk_mdio_write_remapping(addr, MDIO_MMD_VEND2, reg);
+	if (dev < 0)
+		return 0;
+
+	return xpcs_rk_write_reg(pxpcs, dev, reg, val);
+}
+
+static int xpcs_rk_read_c45(struct mii_bus *bus, int addr, int dev, int reg)
+{
+	struct dw_xpcs_rk *pxpcs = bus->priv;
+
+	if (!xpcs_rk_mdio_addr_validate(addr))
+		return -ENODEV;
+
+	dev = xpcs_rk_mdio_read_remapping(addr, dev, reg);
+	if (dev < 0)
+		return 0xffff;
+
+	return xpcs_rk_read_reg(pxpcs, dev, reg);
+}
+
+static int xpcs_rk_write_c45(struct mii_bus *bus, int addr, int dev, int reg, u16 val)
+{
+	struct dw_xpcs_rk *pxpcs = bus->priv;
+
+	if (!xpcs_rk_mdio_addr_validate(addr))
+		return -ENODEV;
+
+	dev = xpcs_rk_mdio_write_remapping(addr, dev, reg);
+	if (dev < 0)
+		return 0;
+
+	return xpcs_rk_write_reg(pxpcs, dev, reg, val);
+}
+
+static struct dw_xpcs_rk *xpcs_rk_create_data(struct platform_device *pdev)
+{
+	struct dw_xpcs_rk *pxpcs;
+
+	pxpcs = devm_kzalloc(&pdev->dev, sizeof(*pxpcs), GFP_KERNEL);
+	if (!pxpcs)
+		return ERR_PTR(-ENOMEM);
+
+	pxpcs->pdev = pdev;
+
+	dev_set_drvdata(&pdev->dev, pxpcs);
+
+	return pxpcs;
+}
+
+static int xpcs_rk_serdes_phy_init(struct dw_xpcs_rk *pxpcs)
+{
+	struct device *dev = &pxpcs->pdev->dev;
+
+	pxpcs->serdes_phy = devm_phy_get(dev, "serdes");
+	if (IS_ERR(pxpcs->serdes_phy))
+		return dev_err_probe(dev, PTR_ERR(pxpcs->serdes_phy),
+					"Failed to get SerDes PHY\n");
+
+	return 0;
+}
+
+static void xpcs_rk_serdes_phy_poweroff(void *data)
+{
+	struct dw_xpcs_rk *pxpcs = data;
+	struct device *dev = &pxpcs->pdev->dev;
+
+	phy_power_off(pxpcs->serdes_phy);
+	phy_exit(pxpcs->serdes_phy);
+
+	dev_pm_genpd_rpm_always_on(dev, false);
+}
+
+static int xpcs_rk_serdes_phy_poweron(struct dw_xpcs_rk *pxpcs)
+{
+	struct device *dev = &pxpcs->pdev->dev;
+	int ret;
+
+	/*
+	 * The power domain is required and must be enabled, which allows us to
+	 * dynamically turn the CSR clock on/off using PM while keeping the PCS
+	 * powered on.
+	 */
+	ret = dev_pm_genpd_rpm_always_on(dev, true);
+	if (ret) {
+		dev_err(dev, "Failed to power on power-domains\n");
+		return ret;
+	}
+
+	ret = phy_init(pxpcs->serdes_phy);
+	if (ret) {
+		dev_err(dev, "Failed to init SerDes PHY\n");
+		goto pm_domain;
+	}
+
+	ret = phy_power_on(pxpcs->serdes_phy);
+	if (ret) {
+		dev_err(dev, "Failed to power on SerDes PHY\n");
+		goto serdes_phy;
+	}
+
+	ret = devm_add_action_or_reset(dev, xpcs_rk_serdes_phy_poweroff, pxpcs);
+	if (ret) {
+		dev_err(dev, "Failed to register devm for SerDes PHY: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+
+serdes_phy:
+	phy_exit(pxpcs->serdes_phy);
+pm_domain:
+	dev_pm_genpd_rpm_always_on(dev, false);
+	return ret;
+}
+
+static int xpcs_rk_init_res(struct dw_xpcs_rk *pxpcs)
+{
+	struct platform_device *pdev = pxpcs->pdev;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "No reg-space found\n");
+		return -EINVAL;
+	}
+
+	if (resource_size(res) < SZ_2M) {
+		dev_err(dev, "Invalid reg-space size\n");
+		return -EINVAL;
+	}
+
+	pxpcs->reg_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(pxpcs->reg_base)) {
+		dev_err(dev, "Failed to map reg-space\n");
+		return PTR_ERR(pxpcs->reg_base);
+	}
+
+	return 0;
+}
+
+static void xpcs_rk_exit_clk(void *data)
+{
+	struct dw_xpcs_rk *pxpcs = data;
+	struct device *dev = &pxpcs->pdev->dev;
+
+	pm_runtime_force_suspend(dev);
+	clk_disable_unprepare(pxpcs->eee_clk);
+}
+
+static int xpcs_rk_init_clk(struct dw_xpcs_rk *pxpcs)
+{
+	struct device *dev = &pxpcs->pdev->dev;
+	unsigned long rate;
+	u64 mult;
+	int ret;
+
+	pxpcs->csr_clk = devm_clk_get(dev, "csr");
+	if (IS_ERR(pxpcs->csr_clk))
+		return dev_err_probe(dev, PTR_ERR(pxpcs->csr_clk),
+					 "Failed to get CSR clock\n");
+
+	pxpcs->eee_clk = devm_clk_get(dev, "eee");
+	if (IS_ERR(pxpcs->eee_clk))
+		return dev_err_probe(dev, PTR_ERR(pxpcs->eee_clk),
+					 "Failed to get EEE clock\n");
+
+	ret = clk_prepare_enable(pxpcs->eee_clk);
+	if (ret) {
+		dev_err(dev, "Failed to enable EEE clock\n");
+		return ret;
+	}
+
+	pm_runtime_set_suspended(dev);
+	pm_runtime_enable(dev);
+
+	ret = devm_add_action_or_reset(dev, xpcs_rk_exit_clk, pxpcs);
+	if (ret) {
+		dev_err(dev, "Failed to register devm for EEE clock: %d\n", ret);
+		return ret;
+	}
+
+	/*
+	 * Compute the multiplier for the EEE clock so that
+	 * clk_eee_period * (mult_fact + 1) falls within 80..120 ns.
+	 *
+	 * On RK3568, clk_xpcs_eee is muxed between gpll200 (200 MHz, 5 ns)
+	 * and cpll125 (125 MHz, 8 ns), selected by CRU_CLKSEL_CON29 bit 13.
+	 * The reset value is 0 (200 MHz), but derive the value at runtime to
+	 * stay correct if the mux is changed by a board.
+	 *
+	 * Use a 64-bit intermediate: on 32-bit builds, 100 * 200000000
+	 * does not fit in unsigned long. Clamp to the 4-bit
+	 * DW_VR_MII_EEE_MULT_FACT_100NS field.
+	 */
+	rate = clk_get_rate(pxpcs->eee_clk);
+	if (!rate)
+		return dev_err_probe(dev, -EINVAL, "Invalid EEE clock rate\n");
+
+	mult = DIV_ROUND_CLOSEST_ULL(100ULL * rate, NSEC_PER_SEC) - 1;
+	pxpcs->eee_mult_fact = min_t(u64, mult, 15);
+	return 0;
+}
+
+static int xpcs_rk_init_bus(struct dw_xpcs_rk *pxpcs)
+{
+	struct device *dev = &pxpcs->pdev->dev;
+	static atomic_t id = ATOMIC_INIT(-1);
+	struct mii_bus *bus;
+	int ret;
+
+	bus = devm_mdiobus_alloc_size(dev, 0);
+	if (!bus)
+		return -ENOMEM;
+
+	bus->name = "Rockchip DW XPCS MCI/APB3";
+	bus->read = xpcs_rk_read_c22;
+	bus->write = xpcs_rk_write_c22;
+	bus->read_c45 = xpcs_rk_read_c45;
+	bus->write_c45 = xpcs_rk_write_c45;
+	bus->phy_mask = ~0;
+	bus->parent = dev;
+	bus->priv = pxpcs;
+
+	snprintf(bus->id, MII_BUS_ID_SIZE,
+		 "rockchip_dwxpcs-%x", atomic_inc_return(&id));
+
+	/*
+	 * MDIO-bus here serves as just a back-end engine abstracting out
+	 * the MDIO and MCI/APB3 IO interfaces utilized for the Rockchip DWXPCS CSRs
+	 * access.
+	 */
+	ret = devm_mdiobus_register(dev, bus);
+	if (ret) {
+		dev_err(dev, "Failed to create MDIO bus\n");
+		return ret;
+	}
+
+	pxpcs->bus = bus;
+	return 0;
+}
+
+static int xpcs_rk_probe(struct platform_device *pdev)
+{
+	struct dw_xpcs_rk *pxpcs;
+	int ret;
+
+	pxpcs = xpcs_rk_create_data(pdev);
+	if (IS_ERR(pxpcs))
+		return PTR_ERR(pxpcs);
+
+	/*
+	 * The XPCS may be attached to a power domain (e.g. PD_PIPE). The domain
+	 * must be powered on before any register access, otherwise the SoC will
+	 * trigger a synchronous external abort (SError).
+	 *
+	 * Accessing the XPCS registers also requires a TX clock from the SerDes,
+	 * which is needed for the soft reset.
+	 */
+	ret = xpcs_rk_serdes_phy_init(pxpcs);
+	if (ret)
+		return ret;
+
+	ret = xpcs_rk_serdes_phy_poweron(pxpcs);
+	if (ret)
+		return ret;
+
+	ret = xpcs_rk_init_res(pxpcs);
+	if (ret)
+		return ret;
+
+	ret = xpcs_rk_init_clk(pxpcs);
+	if (ret)
+		return ret;
+
+	ret = xpcs_rk_init_bus(pxpcs);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct of_device_id xpcs_rk_of_ids[] = {
+	{ .compatible = "rockchip,rk3568-xpcs" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, xpcs_rk_of_ids);
+
+struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np)
+{
+	struct platform_device *pdev;
+	struct device_node *pcs_np;
+	struct dw_xpcs_rk *pxpcs;
+	struct dw_xpcs *xpcs;
+	u32 port;
+
+	if (!of_device_is_available(np))
+		return ERR_PTR(-ENODEV);
+
+	if (of_property_read_u32(np, "reg", &port))
+		return ERR_PTR(-EINVAL);
+
+	if (!xpcs_rk_mdio_addr_validate((int)port))
+		return ERR_PTR(-EINVAL);
+
+	/* The XPCS pdev is attached to the parent node */
+	pcs_np = of_get_parent(np);
+	if (!pcs_np)
+		return ERR_PTR(-ENODEV);
+
+	if (!of_device_is_available(pcs_np)) {
+		of_node_put(pcs_np);
+		return ERR_PTR(-ENODEV);
+	}
+
+	if (!of_match_node(xpcs_rk_of_ids, pcs_np)) {
+		of_node_put(pcs_np);
+		return ERR_PTR(-EINVAL);
+	}
+
+	pdev = of_find_device_by_node(pcs_np);
+	of_node_put(pcs_np);
+	if (!pdev)
+		return ERR_PTR(-EPROBE_DEFER);
+
+	if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) {
+		put_device(&pdev->dev);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	pxpcs = platform_get_drvdata(pdev);
+	if (!pxpcs || !pxpcs->bus) {
+		device_link_remove(dev, &pdev->dev);
+		put_device(&pdev->dev);
+		return ERR_PTR(-EPROBE_DEFER);
+	}
+
+	xpcs = xpcs_create_mdiodev(pxpcs->bus, (int)port);
+	if (IS_ERR(xpcs)) {
+		device_link_remove(dev, &pdev->dev);
+		put_device(&pdev->dev);
+		return xpcs;
+	}
+
+	xpcs_config_eee_mult_fact(xpcs, pxpcs->eee_mult_fact);
+	put_device(&pdev->dev);
+	return xpcs;
+}
+EXPORT_SYMBOL_GPL(xpcs_rk_create);
+
+static int xpcs_rk_pm_runtime_suspend(struct device *dev)
+{
+	struct dw_xpcs_rk *pxpcs = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(pxpcs->csr_clk);
+
+	return 0;
+}
+
+static int xpcs_rk_pm_runtime_resume(struct device *dev)
+{
+	struct dw_xpcs_rk *pxpcs = dev_get_drvdata(dev);
+
+	return clk_prepare_enable(pxpcs->csr_clk);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(xpcs_rk_pm_ops,
+			   xpcs_rk_pm_runtime_suspend,
+			   xpcs_rk_pm_runtime_resume,
+			   NULL);
+
+static struct platform_driver xpcs_rk_driver = {
+	.probe = xpcs_rk_probe,
+	.driver = {
+		.name = "rk_xpcs-dwxpcs",
+		.pm = pm_ptr(&xpcs_rk_pm_ops),
+		.of_match_table = xpcs_rk_of_ids,
+	},
+};
+module_platform_driver(xpcs_rk_driver);
+
+MODULE_DESCRIPTION("Rockchip XPCS platform device driver");
+MODULE_AUTHOR("Coia Prant <coiaprant@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 0337e2bcc0125..8c3875b6985b9 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -761,7 +761,9 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 	 *    DW xPCS used with DW EQoS MAC is always MAC side SGMII.
 	 * 4) VR_MII_DIG_CTRL1 Bit(9) [MAC_AUTO_SW] = 1b (Automatic
 	 *    speed/duplex mode change by HW after SGMII AN complete)
-	 * 5) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 1b (Enable SGMII AN)
+	 * 5) VR_MII_AN_INTR_STS = 0x0 (Clear CL37 AN complete status)
+	 * 6) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 1b (Enable SGMII AN)
+	 *    VR_MII_MMD_CTRL Bit(9) [AN_RESTART] = 1b (Restart SGMII AN)
 	 *
 	 * Note that VR_MII_MMD_CTRL is MII_BMCR.
 	 *
@@ -769,7 +771,14 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 	 *	 SR_MII_AN_ADV. MAC side SGMII receives AN Tx Config from
 	 *	 PHY about the link state change after C28 AN is completed
 	 *	 between PHY and Link Partner. There is also no need to
-	 *	 trigger AN restart for MAC-side SGMII.
+	 *	 trigger AN restart for MAC-side SGMII on most devices.
+	 *
+	 * Note: While the DesignWare databook states that AN restart is
+	 *	 not needed for MAC side SGMII, some implementations (e.g.
+	 *	 Rockchip RK3568) exhibit a timing quirk when integrated with
+	 *	 phylink and do not restart AN automatically when the link
+	 *	 comes back up. An explicit AN restart is required on those
+	 *	 parts to recover the link after a disconnect.
 	 */
 	mdio_ctrl = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMCR);
 	if (mdio_ctrl < 0)
@@ -816,9 +825,14 @@ static int xpcs_config_aneg_c37_sgmii(struct dw_xpcs *xpcs,
 	if (ret < 0)
 		return ret;
 
+	/* Clear CL37 AN complete status */
+	ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);
+	if (ret < 0)
+		return ret;
+
 	if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
 		ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
-				 mdio_ctrl | BMCR_ANENABLE);
+				 mdio_ctrl | BMCR_ANENABLE | BMCR_ANRESTART);
 
 	return ret;
 }
@@ -1093,9 +1107,18 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs,
 		return 0;
 	}
 
-	/* Clear AN complete status or interrupt */
-	if (state->an_complete)
-		xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);
+	if (state->an_complete) {
+		/* Clear AN complete status or interrupt */
+		ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);
+		if (ret < 0)
+			return ret;
+
+		/* Initiate the next round of AN */
+		ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,
+				  BMCR_ANRESTART);
+		if (ret < 0)
+			return ret;
+	}
 
 	return 0;
 }
diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
index 7843356a4dd47..7b867e7520064 100644
--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -186,6 +186,7 @@ struct rockchip_combphy_grfcfg {
 	struct combphy_reg pipe_xpcs_phy_ready;
 	struct combphy_reg pipe_pcie1l0_sel;
 	struct combphy_reg pipe_pcie1l1_sel;
+	struct combphy_reg pipe_sgmii_mac_sel;
 	struct combphy_reg u3otg0_port_en;
 	struct combphy_reg u3otg1_port_en;
 };
@@ -212,6 +213,7 @@ struct rockchip_combphy_priv {
 	bool enable_ssc;
 	bool ext_refclk;
 	struct clk *refclk;
+	u32 sgmii_mac_sel;
 };
 
 static void rockchip_combphy_updatel(struct rockchip_combphy_priv *priv,
@@ -375,6 +377,9 @@ static int rockchip_combphy_parse_dt(struct device *dev, struct rockchip_combphy
 
 	priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
 
+	priv->sgmii_mac_sel = 1;
+	device_property_read_u32(dev, "rockchip,sgmii-mac-sel", &priv->sgmii_mac_sel);
+
 	priv->phy_rst = devm_reset_control_get_exclusive(dev, "phy");
 	/* fallback to old behaviour */
 	if (PTR_ERR(priv->phy_rst) == -ENOENT)
@@ -873,6 +878,8 @@ static int rk3568_combphy_cfg(struct rockchip_combphy_priv *priv)
 		break;
 
 	case PHY_TYPE_SGMII:
+		rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_sgmii_mac_sel,
+					     priv->sgmii_mac_sel > 0);
 		rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_xpcs_phy_ready, true);
 		rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_phymode_sel, true);
 		rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_sel_qsgmii, true);
@@ -984,6 +991,7 @@ static const struct rockchip_combphy_grfcfg rk3568_combphy_grfcfgs = {
 	.con3_for_sata		= { 0x000c, 15, 0, 0x00, 0x4407 },
 	/* pipe-grf */
 	.pipe_con0_for_sata	= { 0x0000, 15, 0, 0x00, 0x2220 },
+	.pipe_sgmii_mac_sel	= { 0x0040, 1, 1, 0x00, 0x01 },
 	.pipe_xpcs_phy_ready	= { 0x0040, 2, 2, 0x00, 0x01 },
 	.u3otg0_port_en		= { 0x0104, 15, 0, 0x0181, 0x1100 },
 	.u3otg1_port_en		= { 0x0144, 15, 0, 0x0181, 0x1100 },
diff --git a/include/linux/pcs/pcs-xpcs-rk.h b/include/linux/pcs/pcs-xpcs-rk.h
new file mode 100644
index 0000000000000..28723d5bd75cc
--- /dev/null
+++ b/include/linux/pcs/pcs-xpcs-rk.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LINUX_PCS_XPCS_ROCKCHIP_H
+#define __LINUX_PCS_XPCS_ROCKCHIP_H
+
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/pcs/pcs-xpcs.h>
+
+struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np);
+
+#endif /* __LINUX_PCS_XPCS_ROCKCHIP_H */

3/2 2026/09/15 16:25 llm patch-evaluator
1m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23071
							output: 48
							thoughts: 1108
						

Thoughts:
**Analyzing the Patch Series for Fuzzing Suitability**

Okay, let's break this down. My initial assessment of this patch series focuses on its feasibility for fuzzing, given my expertise in this area. The target architecture is amd64, which immediately narrows the scope of concern.

First, I need to understand what the patch actually *does*. Looking at the modified files is a good starting point. I see changes across a wide range: device tree bindings (Rockchip and potentially generic), documentation updates (MAINTAINERS), DTS files (RK3568), driver code (stmmac, Rockchip-specific dwmac, PCS drivers, and a Rockchip combphy), Kconfig and Makefiles related to PCS and stmmac. This suggests changes related to Ethernet connectivity, possibly including a new Rockchip XPCS platform.

The files that jump out are the `dwmac-intel.c` file and the introduction of a new `pcs-xpcs-rk.c` file, along with related Kconfig and header files. It looks like we are adding support for a Rockchip XPCS to stmmac via the intel mgbe module.

Now, the critical question is *reachability*. Can this code be executed in standard virtualized environments like QEMU or GCE? This is paramount for fuzzing effectiveness. Specifically, the instruction's section 2 outlines that code MUST NOT be fuzzed if it's structurally unreachable in these environments.

My immediate concern is around `dwmac-intel.c`. Is the code in this file reachable on amd64 in a standard virtualized environment? Is it strictly for physical hardware? I need to know if Intel Ethernet/PCI is emulated in QEMU, or if `stmmac` is typically tied to specific SoC hardware. Does QEMU emulate the relevant parts of the `dwmac-intel` or `stmmac` stack?

The instruction set highlights this as a negative criteria for fuzzing: vendor-specific PCIe switches, SmartNICs, GPU drivers or those that require physical ASIC/PCIe cards that are *not* emulated in standard QEMU. Also important are driver teardown callbacks that rely on a physical PCI hot-unplug or manual unbinding. Additionally, this is likely to apply to code paths exclusive to architectures other than the target architecture.

So, I need to verify that `dwmac-intel` or `stmmac` *can* be reached in a QEMU environment. I need to grep the kernel sources to understand the typical use cases for these drivers. If it's tightly coupled to physical hardware that's not emulated, then fuzzing would be a waste of time and resources and this patch would be non-worthwhile.



4/2 2026/09/15 16:26 tool grepper
0m Args:
{
  "Expression": "MODULE_DEVICE_TABLE.*pci",
  "PathPrefix": "drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c"
}

Results:
{
  "Output": "drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c=1431=static const struct pci_device_id intel_eth_pci_id_table[] = {\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c-1450-};\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c:1451:MODULE_DEVICE_TABLE(pci, intel_eth_pci_id_table);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c-1452-\n"
}

5/2 2026/09/15 16:26 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24972
							output: 49
							thoughts: 25
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into the Intel Ethernet PCI ID Table**

Alright, time to dig into the details. I need to understand how the driver handles PCI device IDs, specifically within the context of Intel Ethernet controllers. Let's start by zeroing in on the `intel_eth_pci_id_table` data structure. That table is defined in `dwmac-intel.c`, so that's where I need to head. I suspect this table holds the key to how the driver recognizes and supports different Intel Ethernet hardware. I'll need to see how it's structured, what fields are included (Vendor ID, Device ID, Subsystem IDs, etc.), and how the driver uses this information during device probe. I'm looking for the logic that determines if a particular PCI device is supported by this driver, and how it then associates the device with the appropriate configuration and setup routines. My goal is a solid grasp of the device matching and initialization process here.



6/2 2026/09/15 16:26 tool read-file
0m Args:
{
  "File": "drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c",
  "FirstLine": 1430,
  "LineCount": 30
}

Results:
{
  "Contents": "1430:\t\n1431:\tstatic const struct pci_device_id intel_eth_pci_id_table[] = {\n1432:\t\t{ PCI_DEVICE_DATA(INTEL, QUARK, \u0026quark_info) },\n1433:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_RGMII1G, \u0026ehl_rgmii1g_info) },\n1434:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_SGMII1G, \u0026ehl_sgmii1g_info) },\n1435:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_SGMII2G5, \u0026ehl_sgmii1g_info) },\n1436:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_PSE0_RGMII1G, \u0026ehl_pse0_rgmii1g_info) },\n1437:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_PSE0_SGMII1G, \u0026ehl_pse0_sgmii1g_info) },\n1438:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_PSE0_SGMII2G5, \u0026ehl_pse0_sgmii1g_info) },\n1439:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_PSE1_RGMII1G, \u0026ehl_pse1_rgmii1g_info) },\n1440:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_PSE1_SGMII1G, \u0026ehl_pse1_sgmii1g_info) },\n1441:\t\t{ PCI_DEVICE_DATA(INTEL, EHL_PSE1_SGMII2G5, \u0026ehl_pse1_sgmii1g_info) },\n1442:\t\t{ PCI_DEVICE_DATA(INTEL, TGL_SGMII1G, \u0026tgl_sgmii1g_phy0_info) },\n1443:\t\t{ PCI_DEVICE_DATA(INTEL, TGLH_SGMII1G_0, \u0026tgl_sgmii1g_phy0_info) },\n1444:\t\t{ PCI_DEVICE_DATA(INTEL, TGLH_SGMII1G_1, \u0026tgl_sgmii1g_phy1_info) },\n1445:\t\t{ PCI_DEVICE_DATA(INTEL, ADLS_SGMII1G_0, \u0026adls_sgmii1g_phy0_info) },\n1446:\t\t{ PCI_DEVICE_DATA(INTEL, ADLS_SGMII1G_1, \u0026adls_sgmii1g_phy1_info) },\n1447:\t\t{ PCI_DEVICE_DATA(INTEL, ADLN_SGMII1G, \u0026adln_sgmii1g_phy0_info) },\n1448:\t\t{ PCI_DEVICE_DATA(INTEL, RPLP_SGMII1G, \u0026adln_sgmii1g_phy0_info) },\n1449:\t\t{}\n1450:\t};\n1451:\tMODULE_DEVICE_TABLE(pci, intel_eth_pci_id_table);\n1452:\t\n1453:\tstatic struct pci_driver intel_eth_pci_driver = {\n1454:\t\t.name = \"intel-eth-pci\",\n1455:\t\t.id_table = intel_eth_pci_id_table,\n1456:\t\t.probe = intel_eth_pci_probe,\n1457:\t\t.remove = intel_eth_pci_remove,\n1458:\t\t.driver         = {\n1459:\t\t\t.pm     = \u0026stmmac_simple_pm_ops,\n"
}

7/2 2026/09/15 16:26 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26193
							output: 42
							thoughts: 696
						

Thoughts:
**Analyzing Network Driver Compatibility on amd64**

Okay, so I'm looking at `dwmac-intel.c`. My initial thought is, this seems specific to Intel Quark, Elkhart Lake, Tiger Lake, Alder Lake, and Raptor Lake integrated MACs. Right, physical Intel PCI devices on those specific Intel SoCs. But then I stop and think, "Does QEMU actually emulate an Intel Quark/EHL/TGL stmmac Ethernet device?" I don't *think* so, but I need to be sure.

Let's check what else is being referenced. I see drivers for Rockchip: `pcs-xpcs-rk.c`, `dwmac-rk.c`, and `phy-rockchip-naneng-combphy.c`, as well as device tree bindings and associated architecture files. These are all clearly for Rockchip SoCs, specifically the RK3568, an ARM64 platform. However, the target architecture is amd64! This means `arch/arm64/...` is *not* compiled or used on the amd64 build I'm working with.

I drill down further: `drivers/net/pcs/pcs-xpcs-rk.c` is a platform driver for Rockchip RK356x SoCs, and requires OpenFirmware device tree and Rockchip hardware. Even if `COMPILE_TEST` enables it, it's matching `rockchip,rk3568-xpcs` which *won't* instantiate on amd64 in QEMU. Same goes for `dwmac-rk.c`, `phy-rockchip-naneng-combphy.c`, `dwmac-renesas-gbeth.c`, `dwmac-rzn1.c`, and `dwmac-socfpga.c`. All platform drivers, targeting specific architectures or hardware that are not relevant to my current amd64 QEMU setup.

So, back to the stmmac question: what about `stmmac_mdio.c`, `pcs-xpcs.c`, and `dwmac-intel.c`? I have to look into `stmmac_mdio.c` to see if there is any virtual or emulated device in it. It's time to dig into the `stmmac` drivers and see if any are emulated by QEMU, or provide virtual interfaces that might be relevant to my analysis. I will now search the kernel tree for drivers that use stmmac.



8/2 2026/09/15 16:27 tool grepper
0m Args:
{
  "Expression": "stmmac_dvr_probe",
  "PathPrefix": "drivers/net/ethernet/stmicro/stmmac/"
}

Results:
{
  "Output": "drivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c=283=static int dwc_eth_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c-329-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c:330:\tret = stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-dwc-qos-eth.c-331-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel-plat.c=42=static int intel_eth_plat_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel-plat.c-117-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel-plat.c:118:\tret = stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel-plat.c-119-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c=1299=static int intel_eth_pci_probe(struct pci_dev *pdev,\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c-1376-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c:1377:\tret = stmmac_dvr_probe(\u0026pdev-\u003edev, plat, \u0026res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c-1378-\tif (ret) {\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c=374=static int ipq806x_gmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c-482-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c:483:\treturn stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c-484-\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c=498=static int loongson_dwmac_probe(struct pci_dev *pdev, const struct pci_device_id *id)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c-561-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c:562:\tret = stmmac_dvr_probe(\u0026pdev-\u003edev, plat, \u0026res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-loongson.c-563-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c=42=static int lpc18xx_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c-67-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c:68:\treturn stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-lpc18xx.c-69-}\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-mediatek.c=599=static int mediatek_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-mediatek.c-641-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-mediatek.c:642:\tret = stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-mediatek.c-643-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-meson.c=47=static int meson6_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-meson.c-72-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-meson.c:73:\treturn stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-meson.c-74-}\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c=382=static int meson8b_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c-463-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c:464:\treturn stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-meson8b.c-465-}\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c=293=static int motorcomm_probe(struct pci_dev *pdev, const struct pci_device_id *id)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c-358-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c:359:\treturn stmmac_dvr_probe(\u0026pdev-\u003edev, plat, \u0026res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-motorcomm.c-360-}\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c=51=static int rzn1_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c-70-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c:71:\tret = stmmac_dvr_probe(dev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c-72-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c=35=static int sophgo_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c-64-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c:65:\treturn stmmac_dvr_probe(dev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sophgo.c-66-}\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c=147=static int spacemit_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c-206-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c:207:\treturn stmmac_dvr_probe(dev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-spacemit.c-208-}\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c=90=static int starfive_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c-152-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c:153:\treturn stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c-154-}\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c=525=static int stm32_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c-578-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c:579:\tret = stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-stm32.c-580-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c=1105=static int sun8i_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c-1201-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c:1202:\t/* the MAC is runtime suspended after stmmac_dvr_probe(), so we\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c-1203-\t * need to ensure the MAC resume back before other operations such\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c-1208-\t/* The mux must be registered after parent MDIO\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c:1209:\t * so after stmmac_dvr_probe()\ndrivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.c-1210-\t */\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c=214=static int tegra_mgbe_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c-375-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c:376:\terr = stmmac_dvr_probe(\u0026pdev-\u003edev, plat, \u0026res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-tegra.c-377-\tif (err \u003c 0)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c=205=static int visconti_eth_dwmac_probe(struct platform_device *pdev)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c-236-\ndrivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c:237:\tret = stmmac_dvr_probe(\u0026pdev-\u003edev, plat_dat, \u0026stmmac_res);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-visconti.c-238-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac.h=411=void stmmac_dvr_remove(struct device *dev);\ndrivers/net/ethernet/stmicro/stmmac/stmmac.h:412:int stmmac_dvr_probe(struct device *device,\ndrivers/net/ethernet/stmicro/stmmac/stmmac.h-413-\t\t     struct plat_stmmacenet_data *plat_dat,\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c=7814=EXPORT_SYMBOL_GPL(stmmac_plat_dat_alloc);\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-7815-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c:7816:static int __stmmac_dvr_probe(struct device *device,\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-7817-\t\t\t      struct plat_stmmacenet_data *plat_dat,\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8119-/**\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c:8120: * stmmac_dvr_probe\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8121- * @dev: device pointer\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8128- */\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c:8129:int stmmac_dvr_probe(struct device *dev, struct plat_stmmacenet_data *plat_dat,\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8130-\t\t     struct stmmac_resources *res)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8139-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c:8140:\tret = __stmmac_dvr_probe(dev, plat_dat, res);\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8141-\tif (ret \u0026\u0026 plat_dat-\u003eexit)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8145-}\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c:8146:EXPORT_SYMBOL_GPL(stmmac_dvr_probe);\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8147-\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_pci.c=118=static int stmmac_pci_probe(struct pci_dev *pdev,\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_pci.c-182-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_pci.c:183:\treturn stmmac_dvr_probe(\u0026pdev-\u003edev, plat, \u0026res);\ndrivers/net/ethernet/stmicro/stmmac/stmmac_pci.c-184-}\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_platform.c=846=int stmmac_pltfr_probe(struct platform_device *pdev,\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_platform.c-854-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_platform.c:855:\treturn stmmac_dvr_probe(\u0026pdev-\u003edev, plat, res);\ndrivers/net/ethernet/stmicro/stmmac/stmmac_platform.c-856-}\n"
}

9/2 2026/09/15 16:27 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31582
							output: 26
							thoughts: 545
						

Thoughts:
**Analyzing Ethernet Device Drivers and QEMU Emulation**

Okay, let's take a look at this list of Ethernet drivers and devices. I see a whole slew of them: `dwmac-dwc-qos-eth`, `dwmac-intel-plat`, `dwmac-intel`, `dwmac-ipq806x`, `dwmac-loongson`, and the list goes on.  It's clear, they're all hardware MAC controllers.  SoC platform devices, mostly, or physical PCI devices.  Makes sense, right?

But then I had a thought. What about QEMU? Does QEMU, the standard x86_64 version, emulate any of these drivers – something like `stmmac`, `dwmac-intel`, or any of those DW XPCS devices?  Let's check what network devices QEMU *does* emulate: `e1000`, `e1000e`, `igb`, etc.  Ah, no sign of the Synopsys or DesignWare devices!  And, thinking about it, even on ARM targets, QEMU emulates other things.  But we're focused on x86_64 here. No, no `stmmac` or `dwmac` emulation to be found!

Alright, next step: let's verify there isn't some sort of virtual bus or software device using these drivers under the hood. Things like USB or network device emulation, that might abstract the underlying controller. Okay, so I should look into what uses `pcs-xpcs`...



10/2 2026/09/15 16:27 tool grepper
0m Args:
{
  "Expression": "xpcs_create",
  "PathPrefix": "drivers/"
}

Results:
{
  "Output": "drivers/net/dsa/microchip/ksz9477.c=321=static int ksz9477_pcs_create(struct ksz_device *dev)\n--\ndrivers/net/dsa/microchip/ksz9477.c-345-\ndrivers/net/dsa/microchip/ksz9477.c:346:\tpcs = xpcs_create_pcs_mdiodev(bus, 0);\ndrivers/net/dsa/microchip/ksz9477.c-347-\tif (IS_ERR(pcs))\n--\ndrivers/net/dsa/sja1105/sja1105_mdio.c=368=static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)\n--\ndrivers/net/dsa/sja1105/sja1105_mdio.c-411-\ndrivers/net/dsa/sja1105/sja1105_mdio.c:412:\t\tpcs = xpcs_create_pcs_mdiodev(bus, port);\ndrivers/net/dsa/sja1105/sja1105_mdio.c-413-\t\tif (IS_ERR(pcs)) {\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c=197=int fbnic_phylink_create(struct net_device *netdev)\n--\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-204-\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c:205:\tpcs = xpcs_create_pcs_mdiodev(fbd-\u003emdio_bus, 0);\ndrivers/net/ethernet/meta/fbnic/fbnic_phylink.c-206-\tif (IS_ERR(pcs)) {\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c=606=static int intel_mgbe_pcs_init(struct stmmac_priv *priv)\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c-615-\t\tpcsnode = fwnode_find_reference(devnode, \"pcs-handle\", 0);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c:616:\t\txpcs = xpcs_create_fwnode(pcsnode);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c-617-\t\tfwnode_handle_put(pcsnode);\n--\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c-619-\t\taddr = ffs(priv-\u003eplat-\u003emdio_bus_data-\u003epcs_mask) - 1;\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c:620:\t\txpcs = xpcs_create_mdiodev(priv-\u003emii, addr);\ndrivers/net/ethernet/stmicro/stmmac/dwmac-intel.c-621-\t}\n--\ndrivers/net/ethernet/wangxun/txgbe/txgbe_phy.c=126=static int txgbe_mdio_pcs_init(struct txgbe *txgbe)\n--\ndrivers/net/ethernet/wangxun/txgbe/txgbe_phy.c-153-\ndrivers/net/ethernet/wangxun/txgbe/txgbe_phy.c:154:\tpcs = xpcs_create_pcs_mdiodev(mii_bus, 0);\ndrivers/net/ethernet/wangxun/txgbe/txgbe_phy.c-155-\tif (IS_ERR(pcs))\n--\ndrivers/net/pcs/pcs-xpcs-rk.c=478=struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np)\n--\ndrivers/net/pcs/pcs-xpcs-rk.c-526-\ndrivers/net/pcs/pcs-xpcs-rk.c:527:\txpcs = xpcs_create_mdiodev(pxpcs-\u003ebus, (int)port);\ndrivers/net/pcs/pcs-xpcs-rk.c-528-\tif (IS_ERR(xpcs)) {\n--\ndrivers/net/pcs/pcs-xpcs.c=1512=static int xpcs_identify(struct dw_xpcs *xpcs)\n--\ndrivers/net/pcs/pcs-xpcs.c-1531-\ndrivers/net/pcs/pcs-xpcs.c:1532:static struct dw_xpcs *xpcs_create_data(struct mdio_device *mdiodev)\ndrivers/net/pcs/pcs-xpcs.c-1533-{\n--\ndrivers/net/pcs/pcs-xpcs.c=1584=static int xpcs_init_id(struct dw_xpcs *xpcs)\n--\ndrivers/net/pcs/pcs-xpcs.c-1598-\ndrivers/net/pcs/pcs-xpcs.c:1599:static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev)\ndrivers/net/pcs/pcs-xpcs.c-1600-{\n--\ndrivers/net/pcs/pcs-xpcs.c-1603-\ndrivers/net/pcs/pcs-xpcs.c:1604:\txpcs = xpcs_create_data(mdiodev);\ndrivers/net/pcs/pcs-xpcs.c-1605-\tif (IS_ERR(xpcs))\n--\ndrivers/net/pcs/pcs-xpcs.c-1635-/**\ndrivers/net/pcs/pcs-xpcs.c:1636: * xpcs_create_mdiodev() - create a DW xPCS instance with the MDIO @addr\ndrivers/net/pcs/pcs-xpcs.c-1637- * @bus: pointer to the MDIO-bus descriptor for the device to be looked at\n--\ndrivers/net/pcs/pcs-xpcs.c-1643- */\ndrivers/net/pcs/pcs-xpcs.c:1644:struct dw_xpcs *xpcs_create_mdiodev(struct mii_bus *bus, int addr)\ndrivers/net/pcs/pcs-xpcs.c-1645-{\n--\ndrivers/net/pcs/pcs-xpcs.c-1652-\ndrivers/net/pcs/pcs-xpcs.c:1653:\txpcs = xpcs_create(mdiodev);\ndrivers/net/pcs/pcs-xpcs.c-1654-\ndrivers/net/pcs/pcs-xpcs.c:1655:\t/* xpcs_create() has taken a refcount on the mdiodev if it was\ndrivers/net/pcs/pcs-xpcs.c:1656:\t * successful. If xpcs_create() fails, this will free the mdio\ndrivers/net/pcs/pcs-xpcs.c-1657-\t * device here. In any case, we don't need to hold our reference\n--\ndrivers/net/pcs/pcs-xpcs.c-1664-}\ndrivers/net/pcs/pcs-xpcs.c:1665:EXPORT_SYMBOL_GPL(xpcs_create_mdiodev);\ndrivers/net/pcs/pcs-xpcs.c-1666-\ndrivers/net/pcs/pcs-xpcs.c:1667:struct phylink_pcs *xpcs_create_pcs_mdiodev(struct mii_bus *bus, int addr)\ndrivers/net/pcs/pcs-xpcs.c-1668-{\n--\ndrivers/net/pcs/pcs-xpcs.c-1670-\ndrivers/net/pcs/pcs-xpcs.c:1671:\txpcs = xpcs_create_mdiodev(bus, addr);\ndrivers/net/pcs/pcs-xpcs.c-1672-\tif (IS_ERR(xpcs))\n--\ndrivers/net/pcs/pcs-xpcs.c-1676-}\ndrivers/net/pcs/pcs-xpcs.c:1677:EXPORT_SYMBOL_GPL(xpcs_create_pcs_mdiodev);\ndrivers/net/pcs/pcs-xpcs.c-1678-\ndrivers/net/pcs/pcs-xpcs.c-1679-/**\ndrivers/net/pcs/pcs-xpcs.c:1680: * xpcs_create_fwnode() - Create a DW xPCS instance from @fwnode\ndrivers/net/pcs/pcs-xpcs.c-1681- * @fwnode: fwnode handle poining to the DW XPCS device\n--\ndrivers/net/pcs/pcs-xpcs.c-1688- */\ndrivers/net/pcs/pcs-xpcs.c:1689:struct dw_xpcs *xpcs_create_fwnode(struct fwnode_handle *fwnode)\ndrivers/net/pcs/pcs-xpcs.c-1690-{\n--\ndrivers/net/pcs/pcs-xpcs.c-1700-\ndrivers/net/pcs/pcs-xpcs.c:1701:\txpcs = xpcs_create(mdiodev);\ndrivers/net/pcs/pcs-xpcs.c-1702-\ndrivers/net/pcs/pcs-xpcs.c:1703:\t/* xpcs_create() has taken a refcount on the mdiodev if it was\ndrivers/net/pcs/pcs-xpcs.c:1704:\t * successful. If xpcs_create() fails, this will free the mdio\ndrivers/net/pcs/pcs-xpcs.c-1705-\t * device here. In any case, we don't need to hold our reference\n--\ndrivers/net/pcs/pcs-xpcs.c-1712-}\ndrivers/net/pcs/pcs-xpcs.c:1713:EXPORT_SYMBOL_GPL(xpcs_create_fwnode);\ndrivers/net/pcs/pcs-xpcs.c-1714-\n"
}

11/2 2026/09/15 16:27 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35056
							output: 41
							thoughts: 1348
						

Thoughts:
**Analysis of XPCS Driver Modifications for Fuzzing Suitability**

Okay, let's break this down systematically. I'm looking at the changes related to `xpcs_create*` functions, and my initial gut feeling is that we're likely *not* going to be able to fuzz this effectively in a standard QEMU environment. The key is to see if any of these changes pass the *negative* criteria.

I've got a list of callers of `xpcs_create*`: ksz9477, sja1105, fbnic, dwmac-intel, txgbe, and pcs-xpcs-rk. Immediately, I see the pattern; none of them are emulated by QEMU, they are all dependent on either physical SoCs or specific PCIe cards that are not standard fare.

Now, let's step through the negative criteria checklist.

1.  We have `Documentation` changes (`.yaml` and `.dts` files), the `MAINTAINERS` file. That's a no-go for fuzzing. These modifications are non-kernel, non-executable, and don't directly impact the compiled kernel image.

2.  The `arch/arm64/boot/dts/rockchip` files are also out, as these are DTS files for an `arm64` Rockchip architecture, and while DT is used on other platforms, this targets ARM, and the target architecture is `amd64`.

3.  The `Kconfig` and `Makefile` changes in the `drivers/net/pcs` and `drivers/net/ethernet/stmicro/stmmac` directories are a grey area. While these affect the build system, they don't directly introduce any new logic that can be tested, so it may or may not disqualify the modification.

4.  Now, the drivers themselves, and here's the kicker: `pcs-xpcs-rk.c`, `phy-rockchip-naneng-combphy.c`, `dwmac-rk.c`, `dwmac-renesas-gbeth.c`, `dwmac-rzn1.c`, `dwmac-socfpga.c`, and `dwmac-intel.c`. These all target specific hardware: Rockchip SoCs, Renesas SoCs, Intel SoC FPGAs, and Intel SoCs. They require *physical* devices or, in the case of `dwmac-intel`, a specific PCIe device that's not going to be available in standard QEMU. That's structurally unreachable hardware, and *definitely* a negative.

5. Finally, we get to the heart of the matter: `drivers/net/pcs/pcs-xpcs.c`. This is where the core XPCS functions are modified. Examining the modifications, I see `xpcs_config_aneg_c37_sgmii` and `xpcs_get_state_c37_sgmii`. However, these are part of the DW XPCS PCS driver, which is only instantiated by drivers for the physical hardware (the devices listed above). So again, we're stuck.

6. The modification to `stmmac_mdio.c` looks interesting: `stmmac_pcs_setup` and `stmmac_pcs_clean`, but they are called from `stmmac_dvr_probe` / `stmmac_dvr_remove`. So, we need to verify where `stmmac_pcs_setup` is called, and, most likely, will be related to these structurally unreachable hardware drivers.

So, after a thorough review, all modifications fall into the "negative criteria" categories. These modifications are either documentation, ARM/Rockchip SoC specific, dependent on physical hardware that's not emulated by QEMU, or part of helper functions that require specific hardware. **In conclusion, I'd say this is not worth fuzzing in a standard QEMU setup.**



12/2 2026/09/15 16:27 tool grepper
0m Args:
{
  "Expression": "stmmac_pcs_setup",
  "PathPrefix": "drivers/net/ethernet/stmicro/stmmac/"
}

Results:
{
  "Output": "drivers/net/ethernet/stmicro/stmmac/stmmac.h=399=void stmmac_mdio_unlock(struct stmmac_priv *priv);\ndrivers/net/ethernet/stmicro/stmmac/stmmac.h:400:int stmmac_pcs_setup(struct net_device *ndev);\ndrivers/net/ethernet/stmicro/stmmac/stmmac.h-401-void stmmac_pcs_clean(struct net_device *ndev);\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c=7816=static int __stmmac_dvr_probe(struct device *device,\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8065-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c:8066:\tret = stmmac_pcs_setup(ndev);\ndrivers/net/ethernet/stmicro/stmmac/stmmac_main.c-8067-\tif (ret)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c=381=int stmmac_mdio_reset(struct mii_bus *bus)\n--\ndrivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c-425-\ndrivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c:426:int stmmac_pcs_setup(struct net_device *ndev)\ndrivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c-427-{\n"
}

13/2 2026/09/15 16:27 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37742
							output: 185
							thoughts: 442
						

Thoughts:
**My Assessment: No Fuzzing Needed Here**

Okay, let's break this down. From what I'm seeing, `stmmac_pcs_setup` is only called during the driver probing phase (`__stmmac_dvr_probe`) within `stmmac_main.c`, and the cleanup counterpart (`stmmac_pcs_clean`) only runs when the driver is being torn down or if probing fails. Now, that `__stmmac_dvr_probe` function? It's strictly for physical stmmac devices – those `dwmac-*` controllers you find on SoCs or physical PCIe cards. And here's the kicker: none of these are emulated in QEMU or Google Compute Engine, which are the standard environments we're considering.

Furthermore, looking at the target architecture (amd64), all those platform drivers specific to ARM, Rockchip, Renesas, SoCFPGA are totally irrelevant. They're non-existent in this context. The Intel PCI driver (`dwmac-intel`) has dependencies on specific Intel hardware (Elkhart Lake, Tiger Lake, Quark, Alder Lake Ethernet controllers) that aren't emulated by the standard QEMU or are available on cloud VMs.

Therefore, the changes here are limited to:

1.  Non-kernel and non-executable stuff: documentation, DT bindings, and the `MAINTAINERS` file.
2.  Code paths that are *only* relevant to architectures other than amd64 (like those `arch/arm64` Device Tree files).
3.  Hardware that's fundamentally unreachable without specific physical ASICs or PCIe hardware that aren't emulated in QEMU or GCE. This includes Rockchip, Renesas, SoC FPGA, and Intel SoC Ethernet controllers and DW XPCS PHY/PCS.

Everything points directly to `WorthFuzzing=false`.

Let's double-check the `set-results` parameters to make sure everything aligns:

*   `WorthFuzzing`: false - *Check!*
*   `Reasoning`: *Clear explanation above; Device Tree, amd64, platform/PCI drivers irrelevant to amd64/QEMU/GCE, structurally unreachable hardware.* - *Check!*
*   `FocusSymbols`: \[ ] (or null/empty) - *Makes sense; nothing to focus on.*
*   `EnableConfigs`: \[ ] (or null/empty) - *Correct; no specific configs are needed.*

Okay, let's go ahead and call `set-results`. It's a no-go for fuzzing in this case.



14/2 2026/09/15 16:27 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces support for the Rockchip RK3568 DesignWare Ethernet PCS (XPCS) and refactors PCS initialization in the STMMAC Ethernet driver family (dwmac-rk, dwmac-intel, dwmac-renesas, dwmac-socfpga, dwmac-rzn1). All modified code is structurally unreachable on amd64 in standard virtualized fuzzing environments (QEMU/GCE): documentation/device tree bindings and arm64 DTS changes are non-executable on amd64, and the affected drivers (stmmac dwmac variants and Synopsys DW XPCS) require physical SoC platform devices or physical Intel/Rockchip/Renesas silicon not emulated by QEMU.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch series introduces support for the Rockchip RK3568 DesignWare Ethernet PCS (XPCS) and refactors PCS initialization in the STMMAC Ethernet driver family (dwmac-rk, dwmac-intel, dwmac-renesas, dwmac-socfpga, dwmac-rzn1). All modified code is structurally unreachable on amd64 in standard virtualized fuzzing environments (QEMU/GCE): documentation/device tree bindings and arm64 DTS changes are non-executable on amd64, and the affected drivers (stmmac dwmac variants and Synopsys DW XPCS) require physical SoC platform devices or physical Intel/Rockchip/Renesas silicon not emulated by QEMU.",
  "WorthFuzzing": false
}

15/1 2026/09/15 16:27 action If
0m Args:
{
  "WorthFuzzing": false
}

Results:
null

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)