* [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS
@ 2026-09-15 12:37 Coia Prant
2026-09-15 12:37 ` [PATCH net-next v6 01/11] net: stmmac: move XPCS lifetime management to platform drivers Coia Prant
` (10 more replies)
0 siblings, 11 replies; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This series adds proper SGMII support for the Rockchip RK3568 SoC
using the integrated Synopsys DesignWare XPCS, along with necessary
fixes and refactoring in the stmmac core and XPCS driver.
Motivation
==========
The RK3568 integrates a DW XPCS accessed via APB3 and connected to
a Naneng Combo SerDes PHY. Several boards (e.g., Ariaboard
Photonicat) use this interface for Gigabit Ethernet. However, the
current upstream stmmac driver does not support this configuration,
and the XPCS driver has issues in SGMII poll mode that cause the
link to be reported incorrectly.
This series addresses these issues by:
- Refactoring stmmac PCS lifetime management to allow platform drivers
full control over PCS creation/destruction
- Fixing the XPCS driver's SGMII link recovery
- Adding a Rockchip XPCS platform glue driver and wiring it up in
dwmac-rk
Series overview
===============
Generic:
Patch 1: move XPCS lifetime management to platform drivers
PHY:
Patch 2: DT binding for Naneng Combo PHY SGMII MAC selection
Patch 3: implement the PHY SGMII MAC selection in driver
RK3568 XPCS/SGMII:
Patch 4: DT binding for Rockchip RK3568 XPCS
Patch 5: add XPCS and fixed-clock nodes to rk3568.dtsi
Patch 6: add ANRESTART support for SGMII link recovery
Patch 7: implement the Rockchip XPCS platform glue driver
Patch 8: DT binding for Rockchip DWMAC PCS
Patch 9: wire up SGMII support in dwmac-rk
Patch 11: update MAINTAINERS
Board enablement:
Patch 10: enable SGMII LAN port on Photonicat board
Changelog
=========
Changes since v5:
Patch 6 (net: pcs: xpcs: add ANRESTART support)
- Document the DW_VR_MII_AN_INTR_STS clear and the return value change
in xpcs_config_aneg_c37_sgmii()
Patch 7 (net: pcs: xpcs: add Rockchip RK3568 platform glue driver)
- Fix eee_mult_fact overflow on 32-bit builds: use a 64-bit intermediate
and clamp the result to the 4-bit DW_VR_MII_EEE_MULT_FACT_100NS field
- Drop the nested device_lock() in xpcs_rk_create() and establish the
device link before reading the supplier's drvdata, to avoid lockdep
recursive-locking warnings. The link is removed explicitly on failure
paths, since DL_FLAG_AUTOREMOVE_CONSUMER only removes it when the
consumer device is released.
- Select GENERIC_PHY and PM_GENERIC_DOMAINS from PCS_XPCS_ROCKCHIP
instead of depending on them, so DWMAC_ROCKCHIP can select it without
unmet-dependency warnings
- Document why the EEE multiplier is derived at runtime rather than
hardcoded (the clock is muxed between 200 MHz and 125 MHz and can be
changed by the board), and the MII write remapping in
xpcs_rk_mdio_write_remapping()
- Add TRM reference for CRU_CLKSEL_CON29
Key design decisions
====================
- The stmmac core now delegates XPCS creation entirely to platform
drivers via pcs_init/pcs_exit. This is necessary because the
generic XPCS creation logic would override any XPCS set up by the
platform driver.
- The Rockchip XPCS driver creates a virtual MDIO bus over the APB3
registers and implements address remapping. The generic XPCS core
handles all PCS configuration via phylink_pcs_ops.
- On RK3568 in SGMII mode, the MAC clock is fixed at 125 MHz and
cannot be dynamically changed. In-band mode is used, and the
generic stmmac set_clk_tx_rate callback is disabled to prevent
incorrect clock updates that would break RX.
- The SerDes and power domain are attached to the XPCS device tree
node rather than the MAC node. This reflects the actual hardware
topology and simplifies the dwmac-rk driver by keeping all PCS-related
resources self-contained. It also prepares for possible future QSGMII
support, where a single SerDes serves multiple MACs and would be
more naturally managed under the XPCS node.
Testing
=======
Board: Ariaboard Photonicat (RK3568)
OS: Armbian (trixie)
Kernel: 6.18 (backports)
Result: The SGMII interface obtains an IP address, SSH works, and
ping traffic passes without loss.
Notes
=====
- When testing out-band mode with set_clk_tx_rate, only 1000Mbps
works on both TX/RX; 10/100Mbps only works on TX side.
Dependencies
============
None. All patches apply cleanly on top of torvalds master tree (v7.3).
Acknowledgments
===============
This work was inspired by and builds upon the excellent work of others:
- Serge Semin's Synopsys DesignWare XPCS platform driver (pcs-xpcs-plat.c)
- Clément Léger's Renesas MIIC driver (pcs-rzn1-miic.c)
- The Rockchip TRM and downstream OEM drivers
Thanks in advance,
Coia Prant
---
Coia Prant (11):
net: stmmac: move XPCS lifetime management to platform drivers
dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel
property
phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
net: pcs: xpcs: add ANRESTART support for SGMII link recovery
net: pcs: xpcs: add Rockchip RK3568 platform glue driver
dt-bindings: net: rockchip-dwmac: document pcs-handle
net: stmmac: dwmac-rk: add SGMII support for RK3568
arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port
MAINTAINERS: add entry for Rockchip XPCS driver
.../net/pcs/rockchip,rk3568-xpcs.yaml | 110 ++++
.../bindings/net/rockchip-dwmac.yaml | 15 +
.../phy/phy-rockchip-naneng-combphy.yaml | 13 +
MAINTAINERS | 9 +
.../boot/dts/rockchip/rk3568-photonicat.dts | 74 ++-
arch/arm64/boot/dts/rockchip/rk3568.dtsi | 45 ++
drivers/net/ethernet/stmicro/stmmac/Kconfig | 1 +
.../net/ethernet/stmicro/stmmac/dwmac-intel.c | 44 +-
.../stmicro/stmmac/dwmac-renesas-gbeth.c | 7 +-
.../net/ethernet/stmicro/stmmac/dwmac-rk.c | 133 +++-
.../net/ethernet/stmicro/stmmac/dwmac-rzn1.c | 7 +-
.../ethernet/stmicro/stmmac/dwmac-socfpga.c | 7 +-
.../net/ethernet/stmicro/stmmac/stmmac_mdio.c | 39 +-
drivers/net/pcs/Kconfig | 25 +
drivers/net/pcs/Makefile | 5 +-
drivers/net/pcs/pcs-xpcs-rk.c | 573 ++++++++++++++++++
drivers/net/pcs/pcs-xpcs.c | 35 +-
.../rockchip/phy-rockchip-naneng-combphy.c | 8 +
include/linux/pcs/pcs-xpcs-rk.h | 11 +
19 files changed, 1089 insertions(+), 72 deletions(-)
create mode 100644 Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml
create mode 100644 drivers/net/pcs/pcs-xpcs-rk.c
create mode 100644 include/linux/pcs/pcs-xpcs-rk.h
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 01/11] net: stmmac: move XPCS lifetime management to platform drivers
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 02/11] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property Coia Prant
` (9 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The current XPCS creation logic in stmmac_pcs_setup() is problematic
for several reasons.
First, if a device tree specifies a "pcs-handle" but no select_pcs()
callback is provided by the platform driver, the created XPCS is never
used. The phylink framework requires select_pcs() to actually return
the PCS to the core, so the pcs-handle property becomes effectively
useless without the matching callback. This is confusing for developers
who expect that specifying a pcs-handle in their device tree should be
sufficient to enable the PCS.
Second, and more critically, when a platform driver sets pcs_init()
and creates an XPCS inside that callback, the common code afterwards
still runs unconditionally and overwrites priv->hw->xpcs with the
local xpcs variable, which stays NULL. The platform driver has no way
to prevent this override because the common code runs after the
platform-specific initialization.
After commit 93f84152e4ae ("net: stmmac: clean up
stmmac_mac_select_pcs()"), the common code no longer falls back to
priv->hw->phylink_pcs if select_pcs() is not set. This change
reinforces that each platform must manage its own PCS life cycle
explicitly, but the XPCS creation code in stmmac_pcs_setup() was not
updated to match this new expectation, leaving a gap where platform
drivers have no clean way to take control of XPCS creation.
Address all of these issues by simplifying the existing pcs_init() and
pcs_exit() dispatch in stmmac_pcs_setup() and stmmac_pcs_clean(). The
common stmmac_pcs_setup() now calls plat->pcs_init() if present, and
stmmac_pcs_clean() calls plat->pcs_exit() if present, removing the
confusing and error-prone XPCS creation logic from the common code.
Platforms that do not need an XPCS simply leave the callbacks as NULL
and no change in behavior occurs. Platforms that do need an XPCS can
now create it with the exact configuration they require, including
wrapping it with custom phylink_pcs_ops when necessary.
The Intel mGbE glue is updated to create its XPCS inside its own
pcs_init() implementation, and the renesas-gbeth, rzn1 and socfpga
pcs_exit() callbacks now explicitly clear priv->hw->phylink_pcs after
destroying the PCS to avoid dangling pointer references.
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../net/ethernet/stmicro/stmmac/dwmac-intel.c | 44 +++++++++++++++++--
.../stmicro/stmmac/dwmac-renesas-gbeth.c | 7 ++-
.../net/ethernet/stmicro/stmmac/dwmac-rzn1.c | 7 ++-
.../ethernet/stmicro/stmmac/dwmac-socfpga.c | 7 ++-
.../net/ethernet/stmicro/stmmac/stmmac_mdio.c | 39 +++-------------
5 files changed, 62 insertions(+), 42 deletions(-)
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-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 {
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 02/11] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
2026-09-15 12:37 ` [PATCH net-next v6 01/11] net: stmmac: move XPCS lifetime management to platform drivers Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 03/11] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568 Coia Prant
` (8 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
On RK3568, the SGMII interface can be routed to either GMAC0 or
GMAC1 via the pipe_sgmii_mac_sel bit in the pipe GRF registers.
Add the optional "rockchip,sgmii-mac-sel" property to allow the
device tree to select which GMAC controller is used for SGMII.
The property takes a value of 0 (GMAC0) or 1 (GMAC1). The hardware
reset value is 1 (GMAC1), but this can be overridden by setting the
property to 0 for boards where SGMII is connected to GMAC0.
This is necessary for boards such as the Ariaboard Photonicat, where
the SGMII interface is connected to GMAC0 and needs to be explicitly
configured.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../bindings/phy/phy-rockchip-naneng-combphy.yaml | 13 +++++++++++++
1 file changed, 13 insertions(+)
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:
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 03/11] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
2026-09-15 12:37 ` [PATCH net-next v6 01/11] net: stmmac: move XPCS lifetime management to platform drivers Coia Prant
2026-09-15 12:37 ` [PATCH net-next v6 02/11] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 04/11] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
` (7 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
On RK3568, the SGMII interface can be routed to either GMAC0 or
GMAC1 via the GRF register pipe_sgmii_mac_sel.
Add support for this selection by introducing
the "rockchip,sgmii-mac-sel" DT property.
From the RK3568 TRM (Part1, Page 229), the PIPE_GRF_XPCS_CON0
bit 1 (pipe_sgmii_mac_sel) is defined as:
0: SGMII routed to GMAC0
1: SGMII routed to GMAC1
The hardware reset value is 1 (GMAC1). If the property is set to 0,
the driver routes SGMII to GMAC0; if set to 1 (or omitted), it
remains at GMAC1.
This is necessary for boards such as the Ariaboard Photonicat, which
uses the SGMII interface connected to GMAC0.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 229)
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
drivers/phy/rockchip/phy-rockchip-naneng-combphy.c | 8 ++++++++
1 file changed, 8 insertions(+)
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 },
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 04/11] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (2 preceding siblings ...)
2026-09-15 12:37 ` [PATCH net-next v6 03/11] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568 Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 05/11] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes Coia Prant
` (6 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
Add device tree binding documentation for the Synopsys DesignWare
XPCS integrated on the Rockchip RK3568 SoC.
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, with four MII ports.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../net/pcs/rockchip,rk3568-xpcs.yaml | 110 ++++++++++++++++++
1 file changed, 110 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml
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>;
+ };
+ };
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 05/11] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (3 preceding siblings ...)
2026-09-15 12:37 ` [PATCH net-next v6 04/11] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 06/11] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
` (5 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The RK3568 SoC integrates a Synopsys DesignWare XPCS that provides
the Physical Coding Sublayer for 1000BASE-X, SGMII, and QSGMII
interfaces via its four MII ports. Add the XPCS device node and
its pcs-mii sub-nodes to the SoC device tree.
The XPCS device is accessed via the APB3 bus at 0xfda00000 and
requires the CSR clock (PCLK_XPCS) for register access and the EEE
clock (CLK_XPCS_EEE) for Energy Efficient Ethernet operation. The
PD_PIPE power domain must be enabled before any register access.
Also add two fixed-clock nodes (xpcs_gmac0_clk and xpcs_gmac1_clk)
providing the 125 MHz reference clock for the GMACs when operating
with XPCS. These clocks are used as the assigned-clock-parents
for the respective GMAC nodes in board-level device trees.
The XPCS node and its pcs-mii sub-nodes are disabled by default and
must be enabled at the board level when 1000BASE-X/SGMII/QSGMII is
in use. The fixed-clock nodes are always present and do not have a
status property, as they are static clock sources.
The XPCS node also requires a reference to the appropriate Naneng
Combo PHY via the phys property at the board level.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
arch/arm64/boot/dts/rockchip/rk3568.dtsi | 45 ++++++++++++++++++++++++
1 file changed, 45 insertions(+)
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>;
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 06/11] net: pcs: xpcs: add ANRESTART support for SGMII link recovery
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (4 preceding siblings ...)
2026-09-15 12:37 ` [PATCH net-next v6 05/11] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 07/11] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
` (4 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc,
Jiawen Wu
On some hardware using the DesignWare XPCS IP (e.g., RK3568 MAC side
SGMII), the PCS does not automatically restart auto-negotiation when the
link goes down and comes back up. Without an explicit ANRESTART, the link
stays down forever.
Add BMCR_ANRESTART in two places:
1. In xpcs_config_aneg_c37_sgmii(), when starting AN, set ANRESTART
alongside ANENABLE to initiate a fresh negotiation.
2. In xpcs_get_state_c37_sgmii(), when link is down and AN completion is
detected, clear the interrupt and trigger ANRESTART to restart the
negotiation process. Propagate the return value of the restart so
errors are not silently ignored.
Also clear DW_VR_MII_AN_INTR_STS in xpcs_config_aneg_c37_sgmii() before
starting AN, matching what xpcs_config_aneg_c37_1000basex() already does.
On the non-inband path the function now returns the result of that write
instead of the DIG_CTRL1 modify.
Update the comment in xpcs_config_aneg_c37_sgmii() to note that although
the DesignWare databook says AN restart is not needed for MAC side SGMII,
some implementations (e.g. Rockchip RK3568) require it to recover the
link after a disconnect.
This is not a fix for an existing mainline platform: the affected
platform (RK3568 XPCS) is introduced later in the same series.
Tested-by: Jiawen Wu <jiawenwu@trustnetic.com>
Tested-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
drivers/net/pcs/pcs-xpcs.c | 35 +++++++++++++++++++++++++++++------
1 file changed, 29 insertions(+), 6 deletions(-)
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;
}
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 07/11] net: pcs: xpcs: add Rockchip RK3568 platform glue driver
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (5 preceding siblings ...)
2026-09-15 12:37 ` [PATCH net-next v6 06/11] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 08/11] dt-bindings: net: rockchip-dwmac: document pcs-handle Coia Prant
` (3 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The RK3568 SoC integrates a Synopsys DesignWare XPCS that is accessed
via APB3 memory-mapped registers.
This driver provides the glue logic to make the XPCS accessible to
the generic pcs-xpcs core.
The XPCS block contains four MII ports (0..3), each of which can be
routed to GMAC0 or GMAC1 via the pcs-handle property in the MAC node.
The hardware maps these ports to different MMDs:
- port 0: MMD 7 (ROCKCHIP_MMD_MII)
- port 1: MMD 2 (ROCKCHIP_MMD_MII1)
- port 2: MMD 3 (ROCKCHIP_MMD_MII2)
- port 3: MMD 4 (ROCKCHIP_MMD_MII3)
This driver creates a virtual MDIO bus that translates MDIO operations
to APB3 register accesses, with proper address remapping for each port.
The generic xpcs driver then creates a phylink_pcs instance on top of
this bus, allowing the MAC to use the PCS via the standard phylink API.
The generic XPCS platform glue (pcs-xpcs-plat.o) is split out of the
pcs_xpcs composite object into its own module, gated behind the new
PCS_XPCS_PLATFORM symbol. The symbol defaults to PCS_XPCS, so existing
configurations keep the snps,dw-xpcs platform glue enabled without any
change.
The EEE multiplier is derived at runtime from the EEE clock rate
instead of being hardcoded, because the clock is muxed between
gpll200 (200 MHz) and cpll125 (125 MHz) and can be changed by the
board or firmware. A 64-bit intermediate avoids overflow on 32-bit
builds, and the result is clamped to the 4-bit
DW_VR_MII_EEE_MULT_FACT_100NS field.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 59, CRU_CLKSEL_CON29)
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part2%20V1.1-20210301.pdf (Page 2078)
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
drivers/net/pcs/Kconfig | 25 ++
drivers/net/pcs/Makefile | 5 +-
drivers/net/pcs/pcs-xpcs-rk.c | 573 ++++++++++++++++++++++++++++++++
include/linux/pcs/pcs-xpcs-rk.h | 11 +
4 files changed, 612 insertions(+), 2 deletions(-)
create mode 100644 drivers/net/pcs/pcs-xpcs-rk.c
create mode 100644 include/linux/pcs/pcs-xpcs-rk.h
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/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 */
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 08/11] dt-bindings: net: rockchip-dwmac: document pcs-handle
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (6 preceding siblings ...)
2026-09-15 12:37 ` [PATCH net-next v6 07/11] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 09/11] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
` (2 subsequent siblings)
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The Rockchip dwmac glue driver now supports SGMII on RK3568 and
requires a PCS, referenced through the "pcs-handle" property. The
property is parsed by rk_pcs_init() and a missing phandle fails the
probe, so describe it in the binding and require it when phy-mode
is "sgmii".
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../devicetree/bindings/net/rockchip-dwmac.yaml | 15 +++++++++++++++
1 file changed, 15 insertions(+)
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:
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 09/11] net: stmmac: dwmac-rk: add SGMII support for RK3568
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (7 preceding siblings ...)
2026-09-15 12:37 ` [PATCH net-next v6 08/11] dt-bindings: net: rockchip-dwmac: document pcs-handle Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 10/11] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port Coia Prant
2026-09-15 12:38 ` [PATCH net-next v6 11/11] MAINTAINERS: add entry for Rockchip XPCS driver Coia Prant
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The RK3568 SoC integrates a Synopsys DesignWare XPCS that can be
connected to GMAC0 or GMAC1 in SGMII mode. Add the necessary glue
logic to support this configuration.
The current dwmac-rk driver does not support SGMII mode. SGMII
requires a PCS to handle auto-negotiation and link state reporting,
but the existing driver only supports RGMII and RMII.
Add a set_to_sgmii() callback to configure the GMAC GRF register for
SGMII mode (bit 7 set, interface selection bits 4:6 ignored when set).
Also add a set_to_rmii() callback for rk3568 to explicitly clear bit 7,
since the new SGMII path leaves it set and the RMII branch previously
relied on the SoC reset value.
Provide pcs_init/pcs_exit callbacks to create/destroy the XPCS via
xpcs_rk_create() from the Rockchip XPCS platform driver, and a
select_pcs callback to return the XPCS to phylink.
Reorder rk_gmac_powerup() so that gmac_clk_enable() is called before
the SGMII check. The SGMII path skips rk_get_phy_intf_sel(), so the
clock must be enabled earlier to cover all register accesses in that
path. While at it, unify the error unwinding into a single
clk_disable label and add error handling for the default (unhandled
interface) case.
SGMII In-band vs Out-of-band
============================
On RK3568, the MAC clock is fixed at 125 MHz and cannot be dynamically
changed by the stmmac core's set_clk_tx_rate callback. In-band mode
works because the PCS handles rate adaptation internally. Out-of-band
mode does not work because the MAC would need to change the clock rate
to 125/12.5/1.25 MHz for 1000/100/10 Mbps respectively, and the clock
is fixed.
Enable default_an_inband for SGMII and disable the generic stmmac
set_clk_tx_rate callback. This forces phylink to use in-band mode,
where the PCS is responsible for speed/duplex negotiation.
Note that default_an_inband can be overridden by a fixed-link node,
and phylink may also fall back to out-of-band if the PHY does not
support in-band signalling. Out-of-band SGMII is not supported by
this driver: the MAC clock would stay at 125 MHz for 10/100 Mbps,
giving working TX but failing RX. Boards must use in-band mode
(managed = "in-band-status" or an in-band capable PHY).
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 386)
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
drivers/net/ethernet/stmicro/stmmac/Kconfig | 1 +
.../net/ethernet/stmicro/stmmac/dwmac-rk.c | 133 +++++++++++++++---
2 files changed, 114 insertions(+), 20 deletions(-)
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-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);
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 10/11] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (8 preceding siblings ...)
2026-09-15 12:37 ` [PATCH net-next v6 09/11] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
@ 2026-09-15 12:37 ` Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:38 ` [PATCH net-next v6 11/11] MAINTAINERS: add entry for Rockchip XPCS driver Coia Prant
10 siblings, 1 reply; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The Ariaboard Photonicat has a Motorcomm YT8521SC Gigabit Ethernet PHY
connected to GMAC0 via XPCS SGMII. Enable the necessary nodes to make
this port functional.
Add rockchip,sgmii-mac-sel = <0> to the already enabled combphy2,
to route the SGMII interface to GMAC0.
Enable the xpcs node and its port 0 sub-node, referencing combphy2
as the SerDes PHY.
Add the mdio0 node with the YT8521SC PHY at address 3, including its
reset GPIO and LED configuration. Also add LED configuration for the
existing RGMII PHY on mdio1 for consistency.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../boot/dts/rockchip/rk3568-photonicat.dts | 74 ++++++++++++++++++-
1 file changed, 72 insertions(+), 2 deletions(-)
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";
+};
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH net-next v6 11/11] MAINTAINERS: add entry for Rockchip XPCS driver
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (9 preceding siblings ...)
2026-09-15 12:37 ` [PATCH net-next v6 10/11] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port Coia Prant
@ 2026-09-15 12:38 ` Coia Prant
10 siblings, 0 replies; 25+ messages in thread
From: Coia Prant @ 2026-09-15 12:38 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
Add a MAINTAINERS entry for the Rockchip RK3568 XPCS platform driver
and its device tree binding.
Include the relevant mailing lists (netdev and linux-rockchip) so that
future patches are properly distributed.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
MAINTAINERS | 9 +++++++++
1 file changed, 9 insertions(+)
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
--
2.47.3
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 01/11] net: stmmac: move XPCS lifetime management to platform drivers
2026-09-15 12:37 ` [PATCH net-next v6 01/11] net: stmmac: move XPCS lifetime management to platform drivers Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-17 20:00 ` Coia Prant
0 siblings, 1 reply; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0
- [Medium] stmmac_pcs_setup()
(drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c:426) loses the only…
Pre-existing issues:
- [Medium] Clock reference leak in the XPCS constructor now invoked from
intel_mgbe_pcs_init(): xpcs_init_clks() (drivers/net/pcs/pcs-…
--
net: stmmac: move XPCS lifetime management to platform drivers
This moves XPCS creation and destruction out of the common
stmmac_pcs_setup()/stmmac_pcs_clean() and into plat->pcs_init()/
plat->pcs_exit(), converts the Intel mGbE glue to create its own XPCS,
and makes the renesas-gbeth, rzn1 and socfpga pcs_exit() callbacks clear
priv->hw->phylink_pcs after destroying the PCS.
> 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);
[Severity: Medium]
This isn't a bug introduced by this patch, but since the XPCS constructor
call now lives here, it may be worth noting: can the failure path inside
xpcs_init_clks() in drivers/net/pcs/pcs-xpcs.c leak the clk references?
xpcs_init_clks() takes the references and, if enabling fails, returns
without putting them back:
ret = clk_bulk_get_optional(dev, DW_XPCS_NUM_CLKS, xpcs->clks);
if (ret)
return dev_err_probe(dev, ret, "Failed to get clocks\n");
ret = clk_bulk_prepare_enable(DW_XPCS_NUM_CLKS, xpcs->clks);
if (ret)
return dev_err_probe(dev, ret, "Failed to enable clocks\n");
xpcs_create() then takes the error path:
ret = xpcs_init_clks(xpcs);
if (ret)
goto out_free_data;
out_free_data calls xpcs_free_data() (mdio_device_put() plus kfree(xpcs)),
so xpcs_clear_clks() - the only caller of clk_bulk_put() - never runs and
the xpcs->clks array is freed with the references still held.
intel_mgbe_pcs_init() only receives an ERR_PTR here, so it has no way to
release them. Would adding clk_bulk_put() on the failed
clk_bulk_prepare_enable() path in xpcs_init_clks() be the right fix?
[ ... ]
> 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;
> }
[Severity: Medium]
This removes the only generic reader of the documented "pcs-handle"
property (and of mdio_bus_data->pcs_mask). For a glue driver that does not
install plat->pcs_init, stmmac_pcs_setup() now returns 0 before looking at
the firmware node at all.
Does that make "pcs-handle" a silent no-op for the plain Synopsys
compatibles handled by dwmac-generic.c?
static const struct of_device_id dwmac_generic_match[] = {
{ .compatible = "snps,dwmac-4.10a"},
{ .compatible = "snps,dwmac"},
{ .compatible = "snps,dwxgmac-2.10"},
dwmac_generic_probe() sets no pcs_init, while "pcs-handle" is defined
generically in Documentation/devicetree/bindings/net/ethernet-controller.yaml
and imported unrestricted by snps,dwmac.yaml. Previously such a node got an
XPCS via xpcs_create_fwnode(); now priv->hw->xpcs stays NULL and the
former probe diagnostic ("No xPCS found" on -EPROBE_DEFER/-ENODEV) is gone
with nothing in its place. Should the bindings be updated, or should a
generic pcs_init helper keep honouring the property?
The commit message says:
First, if a device tree specifies a "pcs-handle" but no select_pcs()
callback is provided by the platform driver, the created XPCS is never
used.
Is that accurate? priv->hw->xpcs has two consumers besides select_pcs().
stmmac_phylink_setup() uses it to widen the interface masks:
/* If we have an xpcs, it defines which PHY interfaces are supported. */
if (priv->hw->xpcs)
pcs = xpcs_to_phylink_pcs(priv->hw->xpcs);
else
pcs = priv->hw->phylink_pcs;
if (pcs)
phy_interface_or(config->supported_interfaces, ...
and stmmac_init_phy() uses it to skip PHY attachment:
if (priv->hw->xpcs &&
xpcs_get_an_mode(priv->hw->xpcs, mode) == DW_AN_C73)
return 0;
The commit message also says:
Platforms that do not need an XPCS simply leave the callbacks as NULL
and no change in behavior occurs.
For a platform that relied on the generic "pcs-handle" path, does behavior
change - narrowed config->supported_interfaces (and config->lpi_interfaces
when priv->dma_cap.eee is set), plus an unconditional PHY attach where the
DW_AN_C73 bypass used to apply?
There appear to be no in-tree DTS files pairing "snps,dwmac" with an
"snps,dw-xpcs" node today (RZ/N1, RZ/T2H, SoCFPGA and Rockchip all go
through glue that sets pcs_init, and pcs_mask is set only by dwmac-intel,
which this patch converts), and the gap is not filled later in the series -
at the end of the series stmmac_mdio.c still only dispatches to
plat->pcs_init and "pcs-handle" is parsed only by dwmac-intel,
dwmac-renesas-gbeth, dwmac-rzn1 and the new dwmac-rk. Could the commit
message state plainly that generic device tree XPCS attachment is being
removed?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 02/11] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property
2026-09-15 12:37 ` [PATCH net-next v6 02/11] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] Concern (verified FALSE POSITIVE, retained only per validation
policy): the binding was said to document `rockchip,sgmii-mac-sel`…
--
dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property
This adds an optional uint32 property rockchip,sgmii-mac-sel to the
naneng-combphy binding, gated so it is only accepted for
rockchip,rk3568-naneng-combphy. The value selects whether SGMII is routed
to GMAC0 (0) or GMAC1 (1), with 1 documented as the hardware reset value.
> 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.
> +
[Severity: Low]
This isn't a bug, just a note on patch ordering for anyone reading this
patch on its own: at this commit there is no in-tree consumer of
rockchip,sgmii-mac-sel yet, so the new ABI looks inert here.
Checking the rest of the series, the consumer does arrive immediately
after in "phy: rockchip: naneng-combphy: add SGMII MAC selection for
RK3568", which parses the property in rockchip_combphy_parse_dt():
priv->sgmii_mac_sel = 1;
device_property_read_u32(dev, "rockchip,sgmii-mac-sel", &priv->sgmii_mac_sel);
and applies it in the PHY_TYPE_SGMII branch of rk3568_combphy_cfg():
rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_sgmii_mac_sel,
priv->sgmii_mac_sel > 0);
with the RK3568 descriptor .pipe_sgmii_mac_sel = { 0x0040, 1, 1, 0x00, 0x01 },
and the Photonicat DT patch then sets rockchip,sgmii-mac-sel = <0> on
&combphy2. So the GMAC0 routing does work at the end of the series and
there is nothing to fix; would it be worth mentioning the driver patch in
this commit message so the dependency is obvious when the binding is
reviewed in isolation?
> [ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 03/11] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
2026-09-15 12:37 ` [PATCH net-next v6 03/11] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568 Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-17 20:02 ` Coia Prant
0 siblings, 1 reply; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 1
- [Low] drivers/phy/rockchip/phy-rockchip-naneng-combphy.c:380-381 reads
'rockchip,sgmii-mac-sel' with device_property_read_u32() for every…
--
phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
This adds a "rockchip,sgmii-mac-sel" DT property so the RK3568 SGMII
interface can be routed to GMAC0 or GMAC1 via the PIPE_GRF_XPCS_CON0
pipe_sgmii_mac_sel bit, defaulting to GMAC1 to match the hardware reset
value.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 229)
> 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
[ ... ]
> @@ -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);
> +
[Severity: Low]
This isn't a bug for any schema-checked device tree, but would it be worth
reacting to the return value here?
The binding restricts the property to two values and to one compatible:
rockchip,sgmii-mac-sel:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [0, 1]
default: 1
...
rockchip,sgmii-mac-sel: true
else:
properties:
rockchip,sgmii-mac-sel: false
In rockchip_combphy_parse_dt() the result of device_property_read_u32() is
dropped, so a malformed property (for example a zero-length or wrong-sized
cell, which returns -ENODATA/-EOVERFLOW and leaves the output untouched)
ends up indistinguishable from an explicit request for GMAC1.
The read also happens for every compatible, including rk3528/rk3562/rk3576/
rk3588 where the binding declares the property invalid; there the value is
parsed and then never used, since only rk3568_combphy_cfg() consumes it.
[ ... ]
> @@ -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);
[Severity: Low]
Should out-of-range values be rejected rather than folded into GMAC1?
The "priv->sgmii_mac_sel > 0" test collapses the u32 to a boolean, so a
device tree with rockchip,sgmii-mac-sel = <2> (or 0xffffffff) that was never
run through dtbs_check gets the GMAC1 routing and rk3568_combphy_cfg() still
returns success, with nothing logged.
A range check in rockchip_combphy_parse_dt() returning -EINVAL, or at least
a dev_warn(), would make such a device tree visible instead of silently
selecting the default route.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 04/11] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
2026-09-15 12:37 ` [PATCH net-next v6 04/11] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 2
- [Medium] The 'required:' list of the new schema mandates 'phys' and
'phy-names', but the SoC-level node added by the companion patch…
- [Low] Concern: the binding invents a per-port child-node ABI
('ethernet-pcs-mii@[0-3]') and mandatory property set that no in-tree…
- [Low] Concern: the binding omits 'resets'/'reset-names' (RK3568 CRU
exposes SRST_XPCS, SRST_XPCS_TX_DIV10, SRST_XPCS_RX_DIV10,…
--
dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
This adds a new YAML binding for the Synopsys DesignWare XPCS block
integrated on the Rockchip RK3568 SoC. The block is described as being
accessed over APB3, fed by a Naneng Combo SerDes PHY, and exposing four
MII ports as child nodes selected through pcs-handle from a MAC node.
A few questions below, mostly about how the required property list lines
up with the dtsi node added in the same series.
> 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 @@
[ ... ]
> + 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.
[Severity: Low]
This isn't a bug, but as a standalone patch this per-port child node
scheme documents an ABI that nothing in the tree consumes yet. With the
rest of the series applied it does line up:
drivers/net/pcs/pcs-xpcs-rk.c:xpcs_rk_create() {
if (of_property_read_u32(np, "reg", &port))
return ERR_PTR(-EINVAL);
...
pcs_np = of_get_parent(np);
...
if (!of_match_node(xpcs_rk_of_ids, pcs_np))
and the consumer side resolves the same node:
drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c:rk_pcs_init() {
pcs_node = of_parse_phandle(np, "pcs-handle", 0);
...
xpcs = xpcs_rk_create(priv->device, pcs_node);
Would it be worth mentioning in the changelog which patch in the series
implements and consumes the ethernet-pcs-mii@N children, so the ABI is
not read as unexercised?
> +
> + 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
[Severity: Medium]
Does making phys and phy-names mandatory here conflict with the SoC node
added by "arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes"?
arch/arm64/boot/dts/rockchip/rk3568.dtsi:
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";
The SerDes link is only filled in at board level:
arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts:
&xpcs {
phys = <&combphy2 PHY_TYPE_SGMII>;
phy-names = "serdes";
status = "okay";
};
dt-validate checks disabled nodes too, so wouldn't make CHECK_DTBS=y
report missing required properties phys and phy-names for the xpcs node
on every rk3568 board dtb other than Photonicat?
Should phys/phy-names be dropped from the required list (or gated), or
should rk3568.dtsi carry them instead?
> +
> +additionalProperties: false
[Severity: Low]
This isn't a bug, but the RK3568 CRU exposes SRST_XPCS,
SRST_XPCS_TX_DIV10, SRST_XPCS_RX_DIV10 and SRST_XPCS_XGXS_RX for this
block, and the schema has no resets/reset-names, while
additionalProperties: false forbids describing them.
Nothing in the tree needs them today: git grep SRST_XPCS at the end of
the series only matches include/dt-bindings/clock/rk3568-cru.h,
drivers/net/pcs/pcs-xpcs-rk.c requests no reset control, and
resynchronisation goes through the in-IP soft reset:
drivers/net/pcs/pcs-xpcs.c:
static int xpcs_soft_reset(struct dw_xpcs *xpcs,
const struct dw_xpcs_compat *compat)
The power-cycle case is also avoided in the driver, which keeps PD_PIPE
powered:
drivers/net/pcs/pcs-xpcs-rk.c:xpcs_rk_serdes_phy_poweron() {
ret = dev_pm_genpd_rpm_always_on(dev, true);
Adding optional resets later stays backward compatible, so this is only
a suggestion: is it intentional to leave the CRU reset lines out of the
binding?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 05/11] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
2026-09-15 12:37 ` [PATCH net-next v6 05/11] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-17 20:15 ` Coia Prant
0 siblings, 1 reply; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 2 · Low: 2
- [Medium] The new `xpcs: ethernet-pcs@fda00000` node in
arch/arm64/boot/dts/rockchip/rk3568.dtsi (line 113) omits `phys` and…
- [Medium] dts: incorrect fixed-clock modeling for XPCS generated clock
- [Low] Concern (invalidated by later patches in the same series):
`compatible = "rockchip,rk3568-xpcs"` matches no driver, the `eee`…
- [Low] The commit message states it adds "two fixed-clock nodes
(xpcs_gmac0_clk and xpcs_gmac1_clk)".
--
arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
This adds an ethernet-pcs node at 0xfda00000 for the RK3568 Synopsys
DesignWare XPCS, along with four ethernet-pcs-mii child nodes, all disabled
by default. It also adds two fixed-clock nodes providing a 125 MHz
reference for the GMACs when running through the XPCS.
No Link: tags present in the commit header.
Two remarks on the changelog first:
> Also add two fixed-clock nodes (xpcs_gmac0_clk and xpcs_gmac1_clk)
> providing the 125 MHz reference clock for the GMACs when operating
[Severity: Low]
Could the names here be updated to match the patch? The nodes added are
clock-xpcs-gmac0 and clock-xpcs-gmac1, labelled clk_gmac0_xpcs_mii and
clk_gmac1_xpcs_mii, and there are no xpcs_gmac0_clk / xpcs_gmac1_clk
identifiers anywhere in the diff.
The exact strings matter for anyone cross-checking the reparenting, since
clock-output-names has to match the CRU mux parent names in
drivers/clk/rockchip/clk-rk3568.c:
PNAME(mux_gmac0_rx_tx_p) = { "clk_gmac0_rgmii_speed", "clk_gmac0_rmii_speed", "clk_gmac0_xpcs_mii" };
PNAME(mux_gmac1_rx_tx_p) = { "clk_gmac1_rgmii_speed", "clk_gmac1_rmii_speed", "clk_gmac1_xpcs_mii" };
> 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";
[Severity: Medium]
Does this node validate against its own binding? The schema added earlier
in this series, Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml,
lists phys and phy-names unconditionally:
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- clocks
- clock-names
- phys
- phy-names
- power-domains
The node here supplies neither. At the tip of the series only
rk3568-photonicat.dts fills them in via an override:
&xpcs {
phys = <&combphy2 PHY_TYPE_SGMII>;
phy-names = "serdes";
status = "okay";
};
Since dt-validate matches by compatible regardless of status, wouldn't
every other rk3568 board DTB now report "'phys' is a required property" and
"'phy-names' is a required property" under make CHECK_DTBS=y?
Would it make sense either to drop phys/phy-names from the binding's
required list (the SerDes phandle is board specific), or to give the dtsi a
default combphy phandle here, given combphy is an on-SoC block?
> +
> + xpcs_mii0: ethernet-pcs-mii@0 {
> + reg = <0>;
> + status = "disabled";
> + };
[Severity: Low]
At this commit alone, compatible "rockchip,rk3568-xpcs" matches no driver,
the eee clock, the PD_PIPE power domain and the four MII children have no
consumer, the two fixed clocks have no user, and dwmac-rk provides no
pcs_init()/select_pcs().
Checking the rest of the series, all of those do land: pcs-xpcs-rk.c adds
static const struct of_device_id xpcs_rk_of_ids[] = {
{ .compatible = "rockchip,rk3568-xpcs" },
{ /* sentinel */ },
};
xpcs_rk_init_clk() consumes both csr and eee, xpcs_rk_serdes_phy_poweron()
consumes the power domain, xpcs_rk_create() reads the child reg and calls
xpcs_create_mdiodev(), dwmac-rk sets pcs_init/pcs_exit/select_pcs with
rk_pcs_init() parsing pcs-handle, and rk3568-photonicat.dts uses both
assigned-clock-parents = <&clk_gmac0_xpcs_mii> and pcs-handle = <&xpcs_mii0>.
So this looks like the usual DT-before-driver split, and the disabled node
is inert at this commit. Is that ordering intentional, so that a bisect
landing here stays functional?
> +
> + xpcs_mii1: ethernet-pcs-mii@1 {
> + reg = <1>;
> + 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>;
> + };
[Severity: Medium]
These two are not board oscillators. Per the changelog the 125 MHz
is the MII reference produced by the XPCS/SerDes block itself, i.e. by
the very node added above, which lives behind RK3568_PD_PIPE and only
runs once that domain is up. Describing it as a root-level fixed-clock
tells the clock framework something different: of_fixed_clk_setup()
registers clk_gmac0_xpcs_mii and clk_gmac1_xpcs_mii at of_clk_init()
time, unconditionally (the nodes deliberately carry no status), and
from then on they always look present, enabled and at rate, with no
link to the XPCS provider or to its power domain.
Where that bites is the reparenting path this patch exists to serve.
A board that sets assigned-clock-parents = <&clk_gmac0_xpcs_mii> on
the GMAC has it applied by of_clk_set_defaults(), which platform_probe()
calls before the GMAC driver's probe() body runs. So mux_gmac0_rx_tx_p
in drivers/clk/rockchip/clk-rk3568.c gets parked on the XPCS leg before
dwmac-rk, and therefore before pcs-xpcs-rk and
xpcs_rk_serdes_phy_poweron(), has touched PD_PIPE. Because there is no
clock provider/consumer relationship to the xpcs node, there is also
nothing for the driver to defer on: -EPROBE_DEFER is not reachable from
of_clk_set_defaults(), and the mux is switched regardless of whether
the XPCS is powered. At best the GMAC is briefly clocked from a dead
source; at worst a CRU mux switch to a stopped parent is not something
I would assume is harmless.
Was the intent to have the xpcs node (or the combphy) be the clock
provider here, with #clock-cells and an entry in the binding, so the
framework tracks the PD_PIPE lifecycle and consumers defer until the
source exists? If instead you have measured that the RK3568 CRU
tolerates being parked on the XPCS leg with PD_PIPE gated, could you
say so explicitly in the changelog and state what keeps the 125 MHz
alive before pcs-xpcs-rk probes? Modelling a generated on-SoC clock
as fixed-clock is the kind of shortcut that tends to be noticed only
when probe order changes.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 06/11] net: pcs: xpcs: add ANRESTART support for SGMII link recovery
2026-09-15 12:37 ` [PATCH net-next v6 06/11] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 2 · Low: 0
- [Medium] In xpcs_get_state_c37_sgmii() (drivers/net/pcs/pcs-
xpcs.c:1110-1121) the AN-complete latch is destroyed before the…
- [Medium] The patch turns the pcs_get_state callback into a writer of
MII_BMCR without any serialization.
--
net: pcs: xpcs: add ANRESTART support for SGMII link recovery
This adds an explicit BMCR_ANRESTART when starting C37 SGMII
auto-negotiation, and also clears DW_VR_MII_AN_INTR_STS plus triggers an
AN restart from the link status path when the link is down but AN
completion is latched. The stated motivation is RK3568 MAC side SGMII,
which does not re-arm AN by itself after a link drop.
> 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
[ ... ]
> @@ -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;
> +
[Severity: Medium]
Can the link stay down permanently here if the restart below fails?
The only condition that lets xpcs_get_state_c37_sgmii() enter this branch is
the latched AN complete bit:
ret = xpcs_read(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS);
...
state->an_complete = ret & DW_VR_MII_AN_STS_C37_ANCMPLT_INTR;
The latch is destroyed by the write above before the restart is attempted.
The restart itself can fail before touching the hardware, because
xpcs_modify() -> mdiodev_c45_modify() -> __mdiobus_c45_modify_changed() does
a read first and bails out on a read error:
ret = __mdiobus_c45_read(bus, addr, devad, regnum);
if (ret < 0)
return ret;
So on a transient bus or CSR access error the sequence is: latch cleared,
restart never issued. On the next poll DW_VR_MII_AN_STS_C37_ANCMPLT_INTR
reads as 0, state->an_complete is false, and this branch is not re-entered.
xpcs_get_state() can only log the error, since the phylink pcs_get_state op
returns void:
if (ret)
dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
"xpcs_get_state_c37_sgmii", ERR_PTR(ret));
No pending-restart state is recorded, and phylink_resolve() will not re-run
pcs_config() while the interface is unchanged. Given the premise of this
patch, that this hardware does not restart AN on its own, would it be better
to issue the ANRESTART before clearing the latch, or to leave the latch
intact (or set a pending-restart flag) when the restart fails so later polls
retry?
> + /* Initiate the next round of AN */
> + ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,
> + BMCR_ANRESTART);
> + if (ret < 0)
> + return ret;
> + }
[Severity: Medium]
Is the pcs_get_state callback allowed to write MII_BMCR without any
serialization against pcs_config?
phylink reaches pcs_get_state from paths with disjoint locks.
phylink_resolve() calls phylink_mac_pcs_get_state() while holding
pl->state_mutex and not RTNL, and in the same section can call
phylink_major_config() -> phylink_pcs_config() -> xpcs_config_aneg_c37_sgmii():
drivers/net/phy/phylink.c:phylink_resolve() {
mutex_lock(&pl->state_mutex);
...
phylink_mac_pcs_get_state(pl, &link_state);
...
phylink_major_config(pl, false, &link_state);
}
While phylink_ethtool_ksettings_get() and phylink_mii_read() reach it with
RTNL only and no state_mutex:
drivers/net/phy/phylink.c:phylink_ethtool_ksettings_get() {
case MLO_AN_INBAND:
if (pl->phydev)
break;
phylink_mac_pcs_get_state(pl, &link_state);
}
struct dw_xpcs has no mutex or spinlock, and mdiodev_c45_modify() only makes
a single register read-modify-write atomic on the bus, so can one CPU be
part-way through the multi-register sequence in xpcs_config_aneg_c37_sgmii():
mdio_ctrl = xpcs_read(xpcs, MDIO_MMD_VEND2, MII_BMCR);
...
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
mdio_ctrl & ~BMCR_ANENABLE);
/* reprogram DW_VR_MII_AN_CTRL, DW_VR_MII_DIG_CTRL1, clear AN_INTR_STS */
...
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, MII_BMCR,
mdio_ctrl | BMCR_ANENABLE | BMCR_ANRESTART);
while the other CPU, in the ethtool or ioctl query path, clears
DW_VR_MII_AN_INTR_STS and sets BMCR_ANRESTART? That would kick the AN state
machine while PCS_MODE/TX_CONFIG are half reprogrammed and AN is
deliberately disabled.
A second variant: both paths read link-down plus AN-complete, and the one
that was descheduled then issues ANRESTART from a stale snapshot, restarting
negotiation on a link that has meanwhile come back up.
The driver already exposes .pcs_an_restart = xpcs_an_restart(), which phylink
calls under state_mutex:
xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,
BMCR_ANRESTART);
Could the restart be driven through that callback instead of open-coding the
same register write in the status path?
>
> return 0;
> }
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 07/11] net: pcs: xpcs: add Rockchip RK3568 platform glue driver
2026-09-15 12:37 ` [PATCH net-next v6 07/11] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 10 potential
issue(s) to consider:
Critical: 0 · High: 2 · Medium: 3 · Low: 5
- [High] xpcs_rk_create() creates an implicitly managed device link
(device_link_add() with only DL_FLAG_AUTOREMOVE_CONSUMER, so…
- [High] xpcs_rk_create() reads another device's driver-private data with
platform_get_drvdata() without holding device_lock(&pdev->dev) and…
- [Medium] PCS_XPCS_ROCKCHIP does 'select PM_GENERIC_DOMAINS' without
'depends on PM'.
- [Medium] Read/write MMD asymmetry for the vendor registers:
xpcs_rk_mdio_read_remapping() maps every MDIO_MMD_VEND2 read by port…
- [Medium] xpcs_get_state_c37_sgmii() now unconditionally clears
DW_VR_MII_AN_INTR_STS and writes BMCR_ANRESTART for every latched AN-…
- [Low] xpcs_rk_create() is exported with no in-tree caller at this
commit (drivers/net/pcs/pcs-xpcs-rk.c), and include/linux/pcs/pcs-…
- [Low] The binding marks phys/phy-names as required and the driver
treats a missing "serdes" PHY as a fatal probe error…
- [Low] The RK3568 binding description advertises QSGMII while the
generic Synopsys XPCS compatibility table has no…
- [Low] When MMD remapping fails (any MMD other than MDIO_MMD_PMAPMD or
MDIO_MMD_VEND2, for which the remappers return -ENXIO), the MDIO bus…
- [Low] net: pcs: integer underflow in EEE multiplier calculation
--
net: pcs: xpcs: add Rockchip RK3568 platform glue driver
This adds a Rockchip RK3568 glue driver that exposes the SoC's APB3-mapped
DesignWare XPCS as a virtual MDIO bus for the generic pcs-xpcs core, with
per-port MMD remapping for the four MII ports. It also splits the generic
platform glue out of the pcs_xpcs composite object behind a new
PCS_XPCS_PLATFORM symbol.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 59, CRU_CLKSEL_CON29)
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part2%20V1.1-20210301.pdf (Page 2078)
A few questions below.
> 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
[Severity: Medium]
Should PCS_XPCS_ROCKCHIP also carry "depends on PM"?
PM_GENERIC_DOMAINS in kernel/power/Kconfig is:
config PM_GENERIC_DOMAINS
bool
depends on PM
Since select does not propagate dependencies, does a COMPILE_TEST or
randconfig build with CONFIG_PM=n force PM_GENERIC_DOMAINS on with unmet
direct dependencies, i.e. the sym_warn_unmet_dep() warning?
The same missing dependency looks functional as well. The APB3/CSR clock
is only ever prepared in the runtime resume callback:
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);
}
which is reachable only through .pm = pm_ptr(&xpcs_rk_pm_ops), and pm_ptr()
is NULL when CONFIG_PM=n. In that configuration
pm_runtime_resume_and_get() in xpcs_rk_read_reg()/xpcs_rk_write_reg()
returns 0 without invoking any callback, so does every readl()/writel() on
reg_base then run with the CSR clock gated? That is the abort case the
probe comment warns about.
Would "depends on PM" (or "depends on PM_GENERIC_DOMAINS") instead of
"select PM_GENERIC_DOMAINS" be the right expression here?
> + 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/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
[ ... ]
> +#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 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;
[Severity: Medium]
Reads of MDIO_MMD_VEND2 are remapped per port address (MMD 7/2/3/4), while
writes of these five registers always go to MMD 7. How does that interact
with the read-modify-write sequences the core performs on exactly these
registers?
For a PCS on port 1, xpcs_do_config() does:
xpcs_modify(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL, mask, val);
which reads MMD 2 and writes the computed value into MMD 7. Is the value
written derived from the register that is actually being modified, and can
the mdio_lock make that pair atomic when the two halves land in different
physical blocks?
Two further cases look related:
- DW_VR_MII_AN_INTR_STS is write-to-clear and is cleared by
xpcs_get_state_c37_sgmii() and by the AN handling paths. With every
port clearing MMD 7, can one port consume another port's latched
AN-complete indication?
- xpcs_config_eee() clears DW_VR_MII_EEE_MCTRL0/1 on disable. With two
ports routed to different GMACs, does disabling EEE on one port clear
the LPI controls of the other?
If these registers really are global to the block, should the provider
arbitrate them (or refuse more than one active port) rather than letting
each phylink_pcs instance program them independently?
> + 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_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);
> +}
[Severity: Low]
When the remapping returns -ENXIO (any MMD other than MDIO_MMD_PMAPMD or
MDIO_MMD_VEND2), the bus callbacks fabricate a result instead of reporting
the error: the read paths return 0xffff and the write paths return 0 as if
the APB3 transaction had happened. The c22 wrappers do the same.
For the modes this glue supports the only reachable user appears to be
xpcs_read_ids(), which tolerates id == 0xffffffff and falls back to
MDIO_MMD_VEND2, so the fabricated value is load-bearing for probe and
returning the errno instead would make xpcs_read_ids() fail with -ENODEV.
Given that, could this behaviour at least get a comment saying that
unsupported MMDs are deliberately faked so probe keeps working? And would
it be better for the instance to advertise only the interfaces this MMD map
can actually serve, so a future C73 path does not silently write into
nothing?
> +
> +static struct dw_xpcs_rk *xpcs_rk_create_data(struct platform_device *pdev)
> +{
[ ... ]
> +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;
> +}
[Severity: Low]
This makes a missing "serdes" phy fatal for probe, and the binding added
earlier in the series lists phys and phy-names under required:, but the
SoC-level node in arch/arm64/boot/dts/rockchip/rk3568.dtsi
(ethernet-pcs@fda00000) has neither property - only
rk3568-photonicat.dts adds:
phys = <&combphy2 PHY_TYPE_SGMII>;
phy-names = "serdes";
Does "make dtbs_check" then report "'phys' is a required property" for
every rk3568 dtb that includes rk3568.dtsi, given that dt-validate also
checks disabled nodes? And does a board that only flips &xpcs to
status = "okay" get an unexplained probe failure here?
Would dropping phys/phy-names from required: (leaving them to board level)
or adding the phys reference to the SoC dtsi be the intended arrangement?
[ ... ]
> +struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np)
> +{
[ ... ]
> + 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);
[Severity: High]
Can device_link_remove() actually drop this link?
device_link_add() is called with only DL_FLAG_AUTOREMOVE_CONSUMER, so
DL_FLAG_STATELESS is absent and the core adds DL_FLAG_MANAGED. In
drivers/base/core.c, device_link_remove() -> device_link_put_kref() then
takes:
if (device_link_test(link, DL_FLAG_STATELESS))
kref_put(&link->kref, __device_link_del);
else if (!device_is_registered(link->consumer))
__device_link_del(&link->kref);
else
WARN(1, "Unable to drop a managed device link reference\n");
The consumer here is the stmmac device, which is registered, so does this
hit the WARN(1) splat (and panic with panic_on_warn) while leaving the link
in place?
The -EPROBE_DEFER path looks easy to reach: "pcs-handle" is not among the
fw_devlink-parsed properties in drivers/of/property.c, so nothing orders
the GMAC probe after the XPCS probe. Whenever the GMAC probes first,
device_link_add() succeeds and platform_get_drvdata() returns NULL.
Would it be simpler to create the link only after the fallible XPCS
creation has succeeded (calling xpcs_destroy() if the link then fails), or
to use device_link_del() / a stateless link consistently?
> + put_device(&pdev->dev);
> + return ERR_PTR(-EPROBE_DEFER);
> + }
> +
> + xpcs = xpcs_create_mdiodev(pxpcs->bus, (int)port);
[Severity: High]
Is reading the supplier's drvdata here safe against the supplier
unbinding?
This runs from another device's probe (rk_pcs_init() during stmmac probe),
without device_lock(&pdev->dev) and without a device_is_bound() check. In
drivers/base/dd.c, device_unbind_cleanup() does:
devres_release_all(dev);
...
device_set_driver(dev, NULL);
dev_set_drvdata(dev, NULL);
So if the XPCS probe fails (for example -EPROBE_DEFER from
devm_phy_get(dev, "serdes")) or the device is unbound while this function
sits between device_link_add() and platform_get_drvdata(), the devm
allocation holding pxpcs is already freed while drvdata still points at
it. Does the "!pxpcs || !pxpcs->bus" test then read freed memory, and can
the freed devm mii_bus be handed to xpcs_create_mdiodev()?
Note also that device_link_add() does not block an unbinding supplier
(device_link_init_status() just assigns DL_STATE_SUPPLIER_UNBIND), and
__device_release_driver() does not re-check for consumers appearing after
its device_links_busy() loop.
Related to this, xpcs_rk_create_data() publishes the structure at the very
start of probe:
pxpcs->pdev = pdev;
dev_set_drvdata(&pdev->dev, pxpcs);
while reg_base, the clocks, eee_mult_fact and bus are filled in later, and
xpcs_rk_init_bus() ends with a plain "pxpcs->bus = bus;". Is the non-NULL
bus field being used as a cross-device readiness flag without any
release/acquire pairing?
Would validating the supplier under device_lock(&pdev->dev) with
device_is_bound(), and holding a reference for the lifetime of the returned
dw_xpcs (the pdev reference is dropped with put_device() before returning,
and mdio_device_create() never registers on the bus), close this?
> + 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);
[ ... ]
One more question about a different patch in the same series, "net: pcs:
xpcs: add ANRESTART support for SGMII link recovery", since it interacts
with this driver:
[Severity: Medium]
In xpcs_get_state_c37_sgmii() (drivers/net/pcs/pcs-xpcs.c) the status
clear was turned into a clear plus an AN restart:
if (state->an_complete) {
/* Clear AN complete status or interrupt */
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);
...
/* Initiate the next round of AN */
ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,
BMCR_ANRESTART);
The changelog describes this as an RK3568 timing quirk, but the code has no
device-ID or quirk gate and no link-transition tracking, so it applies to
every DesignWare XPCS in DW_AN_C37_SGMII mode.
The block is entered whenever DW_VR_MII_AN_STS_C37_ANCMPLT_INTR is set and
DW_VR_MII_C37_ANSGM_SP_LNKSTS is clear, which is a legitimate state (PCS
configuration exchange complete, PHY link still down).
Does that mean an AN restart is issued once per poll interval, for as long
as the attached PHY reports AN complete with link down, on parts that do
not need the workaround? Should the restart be gated on a per-device
quirk?
[ ... ]
> @@ -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");
[Severity: Low]
Is the "- 1" here safe for every rate clk_get_rate() can return?
mult is u64, so if the EEE clock ends up slower than ~5 MHz (a
mis-described clock in DT, a mux left in an unexpected position, a
board that reparents clk_xpcs_eee) then
DIV_ROUND_CLOSEST_ULL(100ULL * rate, NSEC_PER_SEC) evaluates to 0 and
the subtraction wraps to U64_MAX. The clamp on the next line does not
catch that: min_t(u64, mult, 15) compares U64_MAX against 15 and keeps
15, so the slowest possible clock programs the largest
DW_VR_MII_EEE_MULT_FACT_100NS value instead of the smallest. The only
rate that is rejected today is exactly 0, via the !rate check just
above.
So the failure mode is the opposite of what the comment intends:
clk_eee_period * (mult_fact + 1) would be far outside the 80..120 ns
window rather than clamped to the nearest end of it.
Would it be clearer to compute the product first and clamp before
decrementing, e.g. keep the DIV_ROUND_CLOSEST_ULL() result in mult,
clamp it into 1..16, and only then subtract 1? Or, if a rate that low
is simply not a valid configuration for this block, should
xpcs_rk_init_clk() reject it with dev_err_probe() the way it already
rejects rate == 0, rather than silently picking a multiplier?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 08/11] dt-bindings: net: rockchip-dwmac: document pcs-handle
2026-09-15 12:37 ` [PATCH net-next v6 08/11] dt-bindings: net: rockchip-dwmac: document pcs-handle Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 0 · Low: 2
- [Low] Commit message describes code that does not exist at this commit:
the message states the Rockchip dwmac glue "now supports SGMII on…
- [Low] The new `allOf` conditional in
Documentation/devicetree/bindings/net/rockchip-dwmac.yaml keys only on…
--
dt-bindings: net: rockchip-dwmac: document pcs-handle
This adds a pcs-handle property to the Rockchip GMAC binding and a new
allOf conditional making it required whenever phy-mode is "sgmii". Two
observations below, one about the commit message wording and one about
the scope of the new conditional.
> The Rockchip dwmac glue driver now supports SGMII on RK3568 and
> requires a PCS, referenced through the "pcs-handle" property. The
> property is parsed by rk_pcs_init() and a missing phandle fails the
> probe, so describe it in the binding and require it when phy-mode
> is "sgmii".
[Severity: Low]
This isn't a bug, but the wording is in the past tense for code that is not
present yet at this commit. At fc2e19066d08,
drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c has no rk_pcs_init(), no
"pcs-handle" parsing, and no pcs_init/pcs_exit/select_pcs callbacks or
SGMII support.
Checking the end of the series, those all arrive in the following patch
("net: stmmac: dwmac-rk: add SGMII support for RK3568"), which adds
rk_pcs_init() with:
pcs_node = of_parse_phandle(np, "pcs-handle", 0);
if (!pcs_node)
return -ENODEV;
and installs the callbacks in rk_gmac_probe() for
PHY_INTERFACE_MODE_SGMII.
Putting the binding before the driver is the usual convention, so would it
be worth rewording to say the property will be parsed by rk_pcs_init() in
the following patch, rather than that it already is?
> 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
> +
[Severity: Low]
Should this conditional also key on compatible? As written it keys only on
phy-mode, so required: [pcs-handle] applies to all sixteen compatibles this
schema selects (px30, rk3128, rk3228, rk3288, rk3308, rk3328, rk3366,
rk3368, rk3399, rk3506, rk3528, rk3568, rk3576, rk3588, rv1108, rv1126).
At the end of the series only rk3568_ops provides .set_to_sgmii, and
dwmac-rk.c derives the capability from it:
bsp_priv->supports_sgmii = ops->supports_sgmii || !!ops->set_to_sgmii;
so the PCS requirement looks specific to rockchip,rk3568-gmac.
The reverse direction is open too: pcs-handle is added unconditionally, so a
GMAC node on a Rockchip SoC with no XPCS can carry a pcs-handle and still
pass dt_binding_check. The rest of this file already gates SoC-specific
constraints on compatible with a negative else branch, for example:
- if:
properties:
compatible:
contains:
enum:
- rockchip,rk3576-gmac
- rockchip,rk3588-gmac
then:
required:
- rockchip,php-grf
else:
properties:
rockchip,php-grf: false
Documentation/devicetree/bindings/net/renesas,rzv2h-gbeth.yaml uses
"pcs-handle: false" in the else branch for the same purpose. Would nesting
the new block under a check for rockchip,rk3568-gmac, plus
"pcs-handle: false" for the variants without a PCS, describe the hardware
more accurately?
No in-tree DT is affected today: the only Rockchip GMAC node with
phy-mode = "sgmii" is in rk3568-photonicat.dts, and it already provides
pcs-handle = <&xpcs_mii0>.
> unevaluatedProperties: false
>
> examples:
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 09/11] net: stmmac: dwmac-rk: add SGMII support for RK3568
2026-09-15 12:37 ` [PATCH net-next v6 09/11] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 (1) · Low: 5
- [Medium] SGMII is advertised to phylink for every RK3568 GMAC port, but
the SGMII PCS glue and the GRF SGMII mode bit are only wired up when…
- [Low] Claim: setting plat_dat->default_an_inband = true and clearing
plat_dat->set_clk_tx_rate does not enforce in-band SGMII;…
- [Low] Claim: the new "phy-mode = sgmii implies pcs-handle" conditional
in Documentation/devicetree/bindings/net/rockchip-dwmac.yaml is not…
- [Low] The XPCS node added to arch/arm64/boot/dts/rockchip/rk3568.dtsi
(ethernet-pcs@fda00000, lines 113-142) omits `phys` and `phy-names`,…
- [Low] DWMAC_ROCKCHIP now does `select PCS_XPCS_ROCKCHIP` while having
no PM dependency, and PCS_XPCS_ROCKCHIP…
- [Low] Claim: rk3568_set_to_sgmii() and rk3568_set_to_rmii() discard the
regmap_write() status, so a failure to switch GRF CON1 bit 7 leaves…
Pre-existing issues:
- [Medium] gmac_clk_enable(bsp_priv, true) leaks the bulk clock
prepare/enable references when clk_prepare_enable(bsp_priv->clk_phy)…
--
net: stmmac: dwmac-rk: add SGMII support for RK3568
This adds SGMII glue for the RK3568 GMACs: a set_to_sgmii()/set_to_rmii()
pair that programs GRF CON1 bit 7, pcs_init/pcs_exit/select_pcs callbacks
that create the Rockchip XPCS, and default_an_inband plus a NULL
set_clk_tx_rate for SGMII. rk_gmac_powerup() is also reordered so the
clocks are enabled before the interface-specific configuration, with the
error unwinding folded into one clk_disable label.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 386)
A few questions below.
> diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
> index ab3c4cf964239..c6559e749c0ce 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
[Severity: Low]
Can this select produce an unmet dependency warning when PM is disabled?
DWMAC_ROCKCHIP only carries:
depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
while the selected symbol in drivers/net/pcs/Kconfig is:
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
and in kernel/power/Kconfig:
config PM_GENERIC_DOMAINS
bool
depends on PM
With PM=n (for example a COMPILE_TEST build with OF=y and STMMAC_ETH=m),
DWMAC_ROCKCHIP now force-enables PCS_XPCS_ROCKCHIP, which in turn
force-enables PM_GENERIC_DOMAINS against its own depends on PM, so genpd
gets built into a PM=n kernel. Would a conditional select, or a depends
on, be more appropriate here?
Related: PCS_XPCS_ROCKCHIP lives inside the "if PCS_XPCS" block in
drivers/net/pcs/Kconfig, so it also carries depends on PCS_XPCS. Does
DWMAC_ROCKCHIP need to select PCS_XPCS too, to avoid the same kind of
warning when PCS_XPCS=n?
> help
> Support for Ethernet controller on Rockchip RK3288 SoC.
>
> 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
[ ... ]
> @@ -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);
[ ... ]
> @@ -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;
[Severity: Medium]
This isn't a bug introduced by this patch, but since the clock handling in
this function is being reorganised: does gmac_clk_enable(bsp_priv, true)
leak the bulk clock prepare/enable references when the phy clock fails?
static int gmac_clk_enable(struct rk_priv_data *bsp_priv, bool enable)
{
...
ret = clk_bulk_prepare_enable(bsp_priv->num_clks,
bsp_priv->clks);
if (ret)
return ret;
ret = clk_prepare_enable(bsp_priv->clk_phy);
if (ret)
return ret;
...
bsp_priv->clk_enabled = true;
On the second failure the bulk clocks stay prepared/enabled and
bsp_priv->clk_enabled is still false, so neither the bare return here nor
the new clk_disable label (which is gated on clk_enabled) undoes them. A
later successful gmac_clk_enable(true) would then call
clk_bulk_prepare_enable() a second time. Is it worth rolling the bulk
clocks back inside gmac_clk_enable() while this area is being touched?
> +
> + 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 */
[ ... ]
> @@ -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);
>
> @@ -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);
> }
[Severity: Medium]
Is SGMII advertised more widely than the PCS glue is actually installed?
rk_gmac_setup() derives the capability purely from the ops table:
bsp_priv->supports_sgmii = ops->supports_sgmii || !!ops->set_to_sgmii;
rk3568_ops now provides .set_to_sgmii, so supports_sgmii is true for every
rk3568 port and rk_get_interfaces() sets PHY_INTERFACE_MODE_SGMII in
config->supported_interfaces even for a port whose phy-mode is rgmii or
rmii.
The PCS side, however, is keyed off the probe-time phy-mode only (see the
rk_gmac_probe() hunk below), so on such a port priv->plat->select_pcs stays
NULL and no XPCS is ever created. If phylink later picks SGMII out of
supported_interfaces (an SFP cage going through
phylink_sfp_select_interface(), or a PHY reporting
PHY_INTERFACE_MODE_SGMII), stmmac_mac_select_pcs() returns NULL:
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_mac_select_pcs() {
if (priv->plat->select_pcs) {
pcs = priv->plat->select_pcs(priv, interface);
...
}
so nothing rejects the selection. Meanwhile rk_gmac_powerup() ran from the
probe-time bsp_priv->phy_iface, so GRF CON1 bit 7 was cleared by
rk3568_set_to_rgmii()/rk3568_set_to_rmii() and is never set, and
rk_set_clk_tx_rate() is still installed. Would that leave the port with a
link that never comes up and no error reported?
Would gating the __set_bit(PHY_INTERFACE_MODE_SGMII, interfaces) on the same
condition that installs pcs_init/select_pcs, and programming the GRF mode
bit for the interface phylink actually selected, avoid this?
>
> 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);
One more question, about the other patches in the same series rather than
this one:
[Severity: Low]
Does the xpcs node in arch/arm64/boot/dts/rockchip/rk3568.dtsi validate
against the new binding? The binding
Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml has:
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- clocks
- clock-names
- phys
- phy-names
- power-domains
while the SoC-level node has neither phys nor phy-names:
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";
Only rk3568-photonicat.dts adds them at board level, and dt-validate does
not skip nodes with status = "disabled", so would every other rk3568 board
report "'phys' is a required property" under CHECK_DTBS? A board that
enables the node without adding both properties would also fail in
xpcs_rk_serdes_phy_init() -> devm_phy_get(dev, "serdes").
Would it be better to keep the PHY assignment optional in the binding, or to
move the properties into the SoC dtsi?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 10/11] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port
2026-09-15 12:37 ` [PATCH net-next v6 10/11] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port Coia Prant
@ 2026-09-17 18:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 25+ messages in thread
From: netdev-bot+sashiko @ 2026-09-17 18:38 UTC (permalink / raw)
To: coiaprant
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 0
- [Medium] DT binding and SoC dtsi disagree:
Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml…
--
arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port
This enables the SGMII LAN port on the Ariaboard Photonicat by turning on
gmac0, the xpcs node and its port 0 sub-node, adding the YT8521SC PHY on
mdio0, and adding PHY LED nodes for both the new and existing PHYs.
> 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
[ ... ]
> @@ -586,3 +646,13 @@ &xin32k {
> pinctrl-names = "default";
> pinctrl-0 = <&clk32k_out1>;
> };
> +
> +&xpcs {
> + phys = <&combphy2 PHY_TYPE_SGMII>;
> + phy-names = "serdes";
> + status = "okay";
> +};
[Severity: Medium]
Should phys/phy-names live in the board file rather than in rk3568.dtsi?
Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml lists
both of them as mandatory:
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- clocks
- clock-names
- phys
- phy-names
- power-domains
additionalProperties: false
But the SoC node in arch/arm64/boot/dts/rockchip/rk3568.dtsi supplies
neither:
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";
...
};
Since dtschema selects on compatible regardless of status = "disabled",
doesn't every other rk3568 board DTB now fail make CHECK_DTBS=1 with
"'phys' is a required property" and "'phy-names' is a required property",
with rk3568-photonicat being the only board that happens to satisfy the
binding?
The SerDes here also looks SoC-internal rather than board-specific: it is
combphy2 in SGMII mode, the glue driver asks for devm_phy_get(dev,
"serdes"), and the binding's own example already uses
phys = <&combphy2 PHY_TYPE_SGMII>.
Would it be better to put phys/phy-names in rk3568.dtsi, so board files
only flip status and set rockchip,sgmii-mac-sel, or alternatively to drop
the two properties from the binding's required: list?
I checked the following commits in the series and did not find a patch that
adds these properties to rk3568.dtsi or relaxes the binding, and the
baseline has no xpcs node at all, so the disagreement appears to come from
this series.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260915123802.1561724-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 01/11] net: stmmac: move XPCS lifetime management to platform drivers
2026-09-17 18:38 ` netdev-bot+sashiko
@ 2026-09-17 20:00 ` Coia Prant
0 siblings, 0 replies; 25+ messages in thread
From: Coia Prant @ 2026-09-17 20:00 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
<netdev-bot+sashiko@kernel.org> 于2026年9月18日周五 02:38写道:
>
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider:
>
> Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0
>
> - [Medium] stmmac_pcs_setup()
> (drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c:426) loses the only…
>
> Pre-existing issues:
> - [Medium] Clock reference leak in the XPCS constructor now invoked from
> intel_mgbe_pcs_init(): xpcs_init_clks() (drivers/net/pcs/pcs-…
>
> --
>
> net: stmmac: move XPCS lifetime management to platform drivers
>
> This moves XPCS creation and destruction out of the common
> stmmac_pcs_setup()/stmmac_pcs_clean() and into plat->pcs_init()/
> plat->pcs_exit(), converts the Intel mGbE glue to create its own XPCS,
> and makes the renesas-gbeth, rzn1 and socfpga pcs_exit() callbacks clear
> priv->hw->phylink_pcs after destroying the PCS.
>
> > 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);
>
> [Severity: Medium]
> This isn't a bug introduced by this patch, but since the XPCS constructor
> call now lives here, it may be worth noting: can the failure path inside
> xpcs_init_clks() in drivers/net/pcs/pcs-xpcs.c leak the clk references?
>
> xpcs_init_clks() takes the references and, if enabling fails, returns
> without putting them back:
>
> ret = clk_bulk_get_optional(dev, DW_XPCS_NUM_CLKS, xpcs->clks);
> if (ret)
> return dev_err_probe(dev, ret, "Failed to get clocks\n");
>
> ret = clk_bulk_prepare_enable(DW_XPCS_NUM_CLKS, xpcs->clks);
> if (ret)
> return dev_err_probe(dev, ret, "Failed to enable clocks\n");
>
> xpcs_create() then takes the error path:
>
> ret = xpcs_init_clks(xpcs);
> if (ret)
> goto out_free_data;
>
> out_free_data calls xpcs_free_data() (mdio_device_put() plus kfree(xpcs)),
> so xpcs_clear_clks() - the only caller of clk_bulk_put() - never runs and
> the xpcs->clks array is freed with the references still held.
>
> intel_mgbe_pcs_init() only receives an ERR_PTR here, so it has no way to
> release them. Would adding clk_bulk_put() on the failed
> clk_bulk_prepare_enable() path in xpcs_init_clks() be the right fix?
This is a pre-existing bug in pcs-xpcs.c and is not introduced by this
series. It belongs to the XPCS core driver and should be fixed
separately.
> > 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;
> > }
>
> [Severity: Medium]
> This removes the only generic reader of the documented "pcs-handle"
> property (and of mdio_bus_data->pcs_mask). For a glue driver that does not
> install plat->pcs_init, stmmac_pcs_setup() now returns 0 before looking at
> the firmware node at all.
>
> Does that make "pcs-handle" a silent no-op for the plain Synopsys
> compatibles handled by dwmac-generic.c?
>
> static const struct of_device_id dwmac_generic_match[] = {
> { .compatible = "snps,dwmac-4.10a"},
> { .compatible = "snps,dwmac"},
> { .compatible = "snps,dwxgmac-2.10"},
>
> dwmac_generic_probe() sets no pcs_init, while "pcs-handle" is defined
> generically in Documentation/devicetree/bindings/net/ethernet-controller.yaml
> and imported unrestricted by snps,dwmac.yaml. Previously such a node got an
> XPCS via xpcs_create_fwnode(); now priv->hw->xpcs stays NULL and the
> former probe diagnostic ("No xPCS found" on -EPROBE_DEFER/-ENODEV) is gone
> with nothing in its place. Should the bindings be updated, or should a
> generic pcs_init helper keep honouring the property?
>
> The commit message says:
>
> First, if a device tree specifies a "pcs-handle" but no select_pcs()
> callback is provided by the platform driver, the created XPCS is never
> used.
>
> Is that accurate? priv->hw->xpcs has two consumers besides select_pcs().
> stmmac_phylink_setup() uses it to widen the interface masks:
>
> /* If we have an xpcs, it defines which PHY interfaces are supported. */
> if (priv->hw->xpcs)
> pcs = xpcs_to_phylink_pcs(priv->hw->xpcs);
> else
> pcs = priv->hw->phylink_pcs;
>
> if (pcs)
> phy_interface_or(config->supported_interfaces, ...
>
> and stmmac_init_phy() uses it to skip PHY attachment:
>
> if (priv->hw->xpcs &&
> xpcs_get_an_mode(priv->hw->xpcs, mode) == DW_AN_C73)
> return 0;
>
> The commit message also says:
>
> Platforms that do not need an XPCS simply leave the callbacks as NULL
> and no change in behavior occurs.
>
> For a platform that relied on the generic "pcs-handle" path, does behavior
> change - narrowed config->supported_interfaces (and config->lpi_interfaces
> when priv->dma_cap.eee is set), plus an unconditional PHY attach where the
> DW_AN_C73 bypass used to apply?
>
> There appear to be no in-tree DTS files pairing "snps,dwmac" with an
> "snps,dw-xpcs" node today (RZ/N1, RZ/T2H, SoCFPGA and Rockchip all go
> through glue that sets pcs_init, and pcs_mask is set only by dwmac-intel,
> which this patch converts), and the gap is not filled later in the series -
> at the end of the series stmmac_mdio.c still only dispatches to
> plat->pcs_init and "pcs-handle" is parsed only by dwmac-intel,
> dwmac-renesas-gbeth, dwmac-rzn1 and the new dwmac-rk. Could the commit
> message state plainly that generic device tree XPCS attachment is being
> removed?
You're right that this removes the only generic reader of the
"pcs-handle" property, and that dwmac-generic.c no longer gets an XPCS
from it. This is intentional: PCS handling is now the responsibility of
each platform's glue driver via pcs_init(). No in-tree platform relies
on the generic path. I'll state this explicitly in the commit message.
I'll also reword the "created XPCS is never used" sentence to
acknowledge that priv->hw->xpcs is still consulted by
stmmac_phylink_setup() and stmmac_init_phy(), even without select_pcs().
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 03/11] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
2026-09-17 18:38 ` netdev-bot+sashiko
@ 2026-09-17 20:02 ` Coia Prant
0 siblings, 0 replies; 25+ messages in thread
From: Coia Prant @ 2026-09-17 20:02 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
<netdev-bot+sashiko@kernel.org> 于2026年9月18日周五 02:38写道:
>
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider:
>
> Critical: 0 · High: 0 · Medium: 0 · Low: 1
>
> - [Low] drivers/phy/rockchip/phy-rockchip-naneng-combphy.c:380-381 reads
> 'rockchip,sgmii-mac-sel' with device_property_read_u32() for every…
>
> --
>
> phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
>
> This adds a "rockchip,sgmii-mac-sel" DT property so the RK3568 SGMII
> interface can be routed to GMAC0 or GMAC1 via the PIPE_GRF_XPCS_CON0
> pipe_sgmii_mac_sel bit, defaulting to GMAC1 to match the hardware reset
> value.
>
> Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 229)
>
> > 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
>
> [ ... ]
>
> > @@ -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);
> > +
>
> [Severity: Low]
> This isn't a bug for any schema-checked device tree, but would it be worth
> reacting to the return value here?
>
> The binding restricts the property to two values and to one compatible:
>
> rockchip,sgmii-mac-sel:
> $ref: /schemas/types.yaml#/definitions/uint32
> enum: [0, 1]
> default: 1
>
> ...
> rockchip,sgmii-mac-sel: true
> else:
> properties:
> rockchip,sgmii-mac-sel: false
>
> In rockchip_combphy_parse_dt() the result of device_property_read_u32() is
> dropped, so a malformed property (for example a zero-length or wrong-sized
> cell, which returns -ENODATA/-EOVERFLOW and leaves the output untouched)
> ends up indistinguishable from an explicit request for GMAC1.
>
> The read also happens for every compatible, including rk3528/rk3562/rk3576/
> rk3588 where the binding declares the property invalid; there the value is
> parsed and then never used, since only rk3568_combphy_cfg() consumes it.
>
> [ ... ]
>
> > @@ -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);
>
> [Severity: Low]
> Should out-of-range values be rejected rather than folded into GMAC1?
>
> The "priv->sgmii_mac_sel > 0" test collapses the u32 to a boolean, so a
> device tree with rockchip,sgmii-mac-sel = <2> (or 0xffffffff) that was never
> run through dtbs_check gets the GMAC1 routing and rk3568_combphy_cfg() still
> returns success, with nothing logged.
>
> A range check in rockchip_combphy_parse_dt() returning -EINVAL, or at least
> a dev_warn(), would make such a device tree visible instead of silently
> selecting the default route.
Thanks for the review.
The property is validated by dtbschema for all in-tree device trees,
which is where the enum and the compatible gating are enforced. A
malformed or out-of-range value can only come from a bootloader-supplied
DT or an overlay that bypassed dtbs_check, which is a user error rather
than something the driver needs to defend against.
The read result is intentionally ignored: the default of 1 matches the
hardware reset value, so a missing property and an explicit "1" are
semantically identical for this hardware.
Thanks,
Coia
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH net-next v6 05/11] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
2026-09-17 18:38 ` netdev-bot+sashiko
@ 2026-09-17 20:15 ` Coia Prant
0 siblings, 0 replies; 25+ messages in thread
From: Coia Prant @ 2026-09-17 20:15 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
conor+dt, heiko, vkoul, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, prabhakar.mahadev-lad.rj, romain.gantois,
hkallweit1, neil.armstrong, linux, shawn.lin, david, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
<netdev-bot+sashiko@kernel.org> 于2026年9月18日周五 02:38写道:
>
> Thank you for your contribution! Sashiko AI review found 4 potential
> issue(s) to consider:
>
> Critical: 0 · High: 0 · Medium: 2 · Low: 2
>
> - [Medium] The new `xpcs: ethernet-pcs@fda00000` node in
> arch/arm64/boot/dts/rockchip/rk3568.dtsi (line 113) omits `phys` and…
> - [Medium] dts: incorrect fixed-clock modeling for XPCS generated clock
> - [Low] Concern (invalidated by later patches in the same series):
> `compatible = "rockchip,rk3568-xpcs"` matches no driver, the `eee`…
> - [Low] The commit message states it adds "two fixed-clock nodes
> (xpcs_gmac0_clk and xpcs_gmac1_clk)".
>
> --
>
> arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
>
> This adds an ethernet-pcs node at 0xfda00000 for the RK3568 Synopsys
> DesignWare XPCS, along with four ethernet-pcs-mii child nodes, all disabled
> by default. It also adds two fixed-clock nodes providing a 125 MHz
> reference for the GMACs when running through the XPCS.
>
> No Link: tags present in the commit header.
>
> Two remarks on the changelog first:
>
> > Also add two fixed-clock nodes (xpcs_gmac0_clk and xpcs_gmac1_clk)
> > providing the 125 MHz reference clock for the GMACs when operating
>
> [Severity: Low]
> Could the names here be updated to match the patch? The nodes added are
> clock-xpcs-gmac0 and clock-xpcs-gmac1, labelled clk_gmac0_xpcs_mii and
> clk_gmac1_xpcs_mii, and there are no xpcs_gmac0_clk / xpcs_gmac1_clk
> identifiers anywhere in the diff.
>
> The exact strings matter for anyone cross-checking the reparenting, since
> clock-output-names has to match the CRU mux parent names in
> drivers/clk/rockchip/clk-rk3568.c:
>
> PNAME(mux_gmac0_rx_tx_p) = { "clk_gmac0_rgmii_speed", "clk_gmac0_rmii_speed", "clk_gmac0_xpcs_mii" };
> PNAME(mux_gmac1_rx_tx_p) = { "clk_gmac1_rgmii_speed", "clk_gmac1_rmii_speed", "clk_gmac1_xpcs_mii" };
Fixed in v7. The commit message now uses clock-xpcs-gmac0 and
clock-xpcs-gmac1, and mentions that clock-output-names matches the CRU
mux parent names in clk-rk3568.c.
> > 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";
>
> [Severity: Medium]
> Does this node validate against its own binding? The schema added earlier
> in this series, Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml,
> lists phys and phy-names unconditionally:
>
> required:
> - compatible
> - reg
> - "#address-cells"
> - "#size-cells"
> - clocks
> - clock-names
> - phys
> - phy-names
> - power-domains
>
> The node here supplies neither. At the tip of the series only
> rk3568-photonicat.dts fills them in via an override:
>
> &xpcs {
> phys = <&combphy2 PHY_TYPE_SGMII>;
> phy-names = "serdes";
> status = "okay";
> };
>
> Since dt-validate matches by compatible regardless of status, wouldn't
> every other rk3568 board DTB now report "'phys' is a required property" and
> "'phy-names' is a required property" under make CHECK_DTBS=y?
>
> Would it make sense either to drop phys/phy-names from the binding's
> required list (the SerDes phandle is board specific), or to give the dtsi a
> default combphy phandle here, given combphy is an on-SoC block?
dtbs_check only validates required properties for enabled nodes, so the
disabled XPCS node in rk3568.dtsi does not trigger a validation warning.
The SerDes link is a board-level design choice (combphy1 on some boards,
combphy2 on others), so phys/phy-names cannot be provided by the SoC
dtsi. Boards that enable the XPCS must supply them at the board level,
as Photonicat does.
> > +
> > + xpcs_mii0: ethernet-pcs-mii@0 {
> > + reg = <0>;
> > + status = "disabled";
> > + };
>
> [Severity: Low]
> At this commit alone, compatible "rockchip,rk3568-xpcs" matches no driver,
> the eee clock, the PD_PIPE power domain and the four MII children have no
> consumer, the two fixed clocks have no user, and dwmac-rk provides no
> pcs_init()/select_pcs().
>
> Checking the rest of the series, all of those do land: pcs-xpcs-rk.c adds
>
> static const struct of_device_id xpcs_rk_of_ids[] = {
> { .compatible = "rockchip,rk3568-xpcs" },
> { /* sentinel */ },
> };
>
> xpcs_rk_init_clk() consumes both csr and eee, xpcs_rk_serdes_phy_poweron()
> consumes the power domain, xpcs_rk_create() reads the child reg and calls
> xpcs_create_mdiodev(), dwmac-rk sets pcs_init/pcs_exit/select_pcs with
> rk_pcs_init() parsing pcs-handle, and rk3568-photonicat.dts uses both
> assigned-clock-parents = <&clk_gmac0_xpcs_mii> and pcs-handle = <&xpcs_mii0>.
>
> So this looks like the usual DT-before-driver split, and the disabled node
> is inert at this commit. Is that ordering intentional, so that a bisect
> landing here stays functional?
This is the usual DT-before-driver ordering. The driver lands later in
the same series, and the node is disabled until a board enables it.
> > +
> > + clk_gmac0_xpcs_mii: clock-xpcs-gmac0 {
> > + compatible = "fixed-clock";
> > + clock-frequency = <125000000>;
> > + clock-output-names = "clk_gmac0_xpcs_mii";
> > + #clock-cells = <0>;
> > + };
>
> [Severity: Medium]
> These two are not board oscillators. Per the changelog the 125 MHz
> is the MII reference produced by the XPCS/SerDes block itself, i.e. by
> the very node added above, which lives behind RK3568_PD_PIPE and only
> runs once that domain is up. Describing it as a root-level fixed-clock
> tells the clock framework something different: of_fixed_clk_setup()
> registers clk_gmac0_xpcs_mii and clk_gmac1_xpcs_mii at of_clk_init()
> time, unconditionally (the nodes deliberately carry no status), and
> from then on they always look present, enabled and at rate, with no
> link to the XPCS provider or to its power domain.
>
> Where that bites is the reparenting path this patch exists to serve.
> A board that sets assigned-clock-parents = <&clk_gmac0_xpcs_mii> on
> the GMAC has it applied by of_clk_set_defaults(), which platform_probe()
> calls before the GMAC driver's probe() body runs. So mux_gmac0_rx_tx_p
> in drivers/clk/rockchip/clk-rk3568.c gets parked on the XPCS leg before
> dwmac-rk, and therefore before pcs-xpcs-rk and
> xpcs_rk_serdes_phy_poweron(), has touched PD_PIPE. Because there is no
> clock provider/consumer relationship to the xpcs node, there is also
> nothing for the driver to defer on: -EPROBE_DEFER is not reachable from
> of_clk_set_defaults(), and the mux is switched regardless of whether
> the XPCS is powered. At best the GMAC is briefly clocked from a dead
> source; at worst a CRU mux switch to a stopped parent is not something
> I would assume is harmless.
>
> Was the intent to have the xpcs node (or the combphy) be the clock
> provider here, with #clock-cells and an entry in the binding, so the
> framework tracks the PD_PIPE lifecycle and consumers defer until the
> source exists? If instead you have measured that the RK3568 CRU
> tolerates being parked on the XPCS leg with PD_PIPE gated, could you
> say so explicitly in the changelog and state what keeps the 125 MHz
> alive before pcs-xpcs-rk probes? Modelling a generated on-SoC clock
> as fixed-clock is the kind of shortcut that tends to be noticed only
> when probe order changes.
The XPCS block is not a clock controller: it has no register interface
to configure or control the 125 MHz output, so there is no way to
implement a proper clock provider for it. fixed-clock is the correct
description of a fixed-rate on-SoC clock.
On the reparenting concern: the GMAC probe is serialized behind the
XPCS probe by the EPROBE_DEFER chain. rk_pcs_init() calls
xpcs_rk_create(), which returns -EPROBE_DEFER until the XPCS device has
probed and its MDIO bus is up. Until then, the GMAC probe never reaches
the point where the clock is used, so there is no window where the GMAC
runs from an unpowered source.
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-09-17 20:15 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-15 12:37 [PATCH net-next v6 00/11] net: pcs: add basic support for RK3568 XPCS Coia Prant
2026-09-15 12:37 ` [PATCH net-next v6 01/11] net: stmmac: move XPCS lifetime management to platform drivers Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-17 20:00 ` Coia Prant
2026-09-15 12:37 ` [PATCH net-next v6 02/11] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 03/11] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568 Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-17 20:02 ` Coia Prant
2026-09-15 12:37 ` [PATCH net-next v6 04/11] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 05/11] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-17 20:15 ` Coia Prant
2026-09-15 12:37 ` [PATCH net-next v6 06/11] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 07/11] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 08/11] dt-bindings: net: rockchip-dwmac: document pcs-handle Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 09/11] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:37 ` [PATCH net-next v6 10/11] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port Coia Prant
2026-09-17 18:38 ` netdev-bot+sashiko
2026-09-15 12:38 ` [PATCH net-next v6 11/11] MAINTAINERS: add entry for Rockchip XPCS driver Coia Prant
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®