* [PATCH net-next v11 1/6] net: pcs: xpcs: add ANRESTART support for SGMII link recovery
2026-10-08 17:51 [PATCH net-next v11 0/6] net: pcs: add basic support for RK3568 XPCS Coia Prant
@ 2026-10-08 17:51 ` Coia Prant
2026-10-12 19:01 ` netdev-bot+sashiko
2026-10-08 17:51 ` [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
` (4 subsequent siblings)
5 siblings, 1 reply; 18+ messages in thread
From: Coia Prant @ 2026-10-08 17:51 UTC (permalink / raw)
To: Jakub Kicinski, netdev
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
Heiner Kallweit, Russell King, David Wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel, Coia Prant, 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.
The restart cannot go through the .pcs_an_restart op: phylink only
calls it for 802.3z interfaces, and SGMII is not one. Changing hardware
state from pcs_get_state() is already done elsewhere in this driver
(xpcs_get_state_c73() calls xpcs_soft_reset() and xpcs_do_config()),
so the same pattern is used here.
The latch is cleared before issuing the restart, not after: clearing it
afterwards would discard a freshly latched ANCMPLT from the new
negotiation. If an MDIO access fails at this point, it indicates an
unrecoverable hardware condition until reset.
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 b415b93d77c15..6466e0ff2a98b 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 related [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 1/6] net: pcs: xpcs: add ANRESTART support for SGMII link recovery
2026-10-08 17:51 ` [PATCH net-next v11 1/6] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
@ 2026-10-12 19:01 ` netdev-bot+sashiko
2026-10-12 19:24 ` Coia Prant
0 siblings, 1 reply; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 19:01 UTC (permalink / raw)
To: coiaprant
Cc: kuba, netdev, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, hkallweit1, linux, david.wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 1
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] xpcs_get_state_c37_sgmii() now does a non-atomic read -> act
sequence.
- [Low] Concern: in xpcs_get_state_c37_sgmii() the ANCMPLT latch is
cleared (pcs-xpcs.c:1112) before BMCR_ANRESTART is issued (pcs-…
--
net: pcs: xpcs: add ANRESTART support for SGMII link recovery
This patch sets BMCR_ANRESTART alongside BMCR_ANENABLE when configuring
C37 SGMII AN. It also clears DW_VR_MII_AN_INTR_STS before AN starts. In
xpcs_get_state_c37_sgmii(), when AN completion is seen with the link down,
it clears the ANCMPLT latch and restarts AN, so that parts such as RK3568
can recover the link.
> diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
> index b415b93d77c15..6466e0ff2a98b 100644
> --- a/drivers/net/pcs/pcs-xpcs.c
> +++ b/drivers/net/pcs/pcs-xpcs.c
[ ... ]
> @@ -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;
[Severity: Medium]
Can two callers of .pcs_get_state() race here and restart a negotiation
that has just finished with the link up?
The read of DW_VR_MII_AN_INTR_STS at the top of the function and the
clear and restart here are not atomic. phylink calls .pcs_get_state()
from contexts that do not share a lock:
phylink_resolve() holds pl->state_mutex, not RTNL
phylink_ethtool_ksettings_get() holds RTNL only (MLO_AN_INBAND,
no phydev)
phylink_mii_ioctl()->phylink_mii_read() holds RTNL only
struct dw_xpcs has no lock of its own, and the MDIO bus lock only covers
each single access.
A possible interleaving:
CPU1 (ethtool get) CPU0 (phylink_resolve)
xpcs_read(INTR_STS)
link=0, ANCMPLT=1
xpcs_read(INTR_STS)
link=0, ANCMPLT=1
xpcs_write(INTR_STS, 0)
xpcs_modify(BMCR, ANRESTART)
... AN completes, link up ...
xpcs_write(INTR_STS, 0)
xpcs_modify(BMCR, ANRESTART)
CPU1 acts on its stale sample. It clears the newly latched ANCMPLT and
restarts AN on a link that has just come up.
Before this patch, a concurrent caller could only clear the latch. The
extra restart is new. Unprivileged users can issue ethtool get link
settings.
The commit message's reason for clearing the latch before the restart
assumes that get_state calls are serialized. Does phylink actually
guarantee that for these callers?
[Severity: Low]
What retries the restart if the xpcs_write() of DW_VR_MII_AN_INTR_STS
succeeds and then this xpcs_modify() of MII_BMCR fails?
The latched ANCMPLT that triggers this path has already been cleared. No
restart is pending, and xpcs_get_state() only logs the error:
phylink_resolve()
xpcs_get_state()
xpcs_get_state_c37_sgmii()
xpcs_write(DW_VR_MII_AN_INTR_STS, 0) succeeds
xpcs_modify(MII_BMCR, BMCR_ANRESTART) fails
dev_err(..., "xpcs_get_state_c37_sgmii", ...)
On parts that do not restart AN by themselves, the link would then stay
down until the interface is reconfigured.
The commit message covers this case:
"If an MDIO access fails at this point, it indicates an unrecoverable
hardware condition until reset."
On RK3568, the MMIO-based MDIO back end added later in the series seems
to fail only through pm_runtime_resume_and_get(). That failure is not
transient. On other XPCS platforms the databook says AN restarts on its
own. So the documented behaviour may be fine as it is.
> + }
>
> return 0;
> }
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008175225.3375274-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 1/6] net: pcs: xpcs: add ANRESTART support for SGMII link recovery
2026-10-12 19:01 ` netdev-bot+sashiko
@ 2026-10-12 19:24 ` Coia Prant
0 siblings, 0 replies; 18+ messages in thread
From: Coia Prant @ 2026-10-12 19:24 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: kuba, netdev, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, hkallweit1, linux, david.wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel
Thanks. Both points were raised in earlier versions and are intentional
for this hardware.
<netdev-bot+sashiko@kernel.org> 于2026年10月13日周二 03:01写道:
>
> Thank you for your contribution! Sashiko AI review found 2 potential
> issue(s) to consider.
>
> Critical: 0 · High: 0 · Medium: 1 · Low: 1
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [Medium] xpcs_get_state_c37_sgmii() now does a non-atomic read -> act
> sequence.
> - [Low] Concern: in xpcs_get_state_c37_sgmii() the ANCMPLT latch is
> cleared (pcs-xpcs.c:1112) before BMCR_ANRESTART is issued (pcs-…
>
> --
>
> net: pcs: xpcs: add ANRESTART support for SGMII link recovery
>
> This patch sets BMCR_ANRESTART alongside BMCR_ANENABLE when configuring
> C37 SGMII AN. It also clears DW_VR_MII_AN_INTR_STS before AN starts. In
> xpcs_get_state_c37_sgmii(), when AN completion is seen with the link down,
> it clears the ANCMPLT latch and restarts AN, so that parts such as RK3568
> can recover the link.
>
> > diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
> > index b415b93d77c15..6466e0ff2a98b 100644
> > --- a/drivers/net/pcs/pcs-xpcs.c
> > +++ b/drivers/net/pcs/pcs-xpcs.c
>
> [ ... ]
>
> > @@ -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;
>
> [Severity: Medium]
> Can two callers of .pcs_get_state() race here and restart a negotiation
> that has just finished with the link up?
>
> The read of DW_VR_MII_AN_INTR_STS at the top of the function and the
> clear and restart here are not atomic. phylink calls .pcs_get_state()
> from contexts that do not share a lock:
>
> phylink_resolve() holds pl->state_mutex, not RTNL
> phylink_ethtool_ksettings_get() holds RTNL only (MLO_AN_INBAND,
> no phydev)
> phylink_mii_ioctl()->phylink_mii_read() holds RTNL only
>
> struct dw_xpcs has no lock of its own, and the MDIO bus lock only covers
> each single access.
>
> A possible interleaving:
>
> CPU1 (ethtool get) CPU0 (phylink_resolve)
> xpcs_read(INTR_STS)
> link=0, ANCMPLT=1
> xpcs_read(INTR_STS)
> link=0, ANCMPLT=1
> xpcs_write(INTR_STS, 0)
> xpcs_modify(BMCR, ANRESTART)
> ... AN completes, link up ...
> xpcs_write(INTR_STS, 0)
> xpcs_modify(BMCR, ANRESTART)
>
> CPU1 acts on its stale sample. It clears the newly latched ANCMPLT and
> restarts AN on a link that has just come up.
>
> Before this patch, a concurrent caller could only clear the latch. The
> extra restart is new. Unprivileged users can issue ethtool get link
> settings.
>
> The commit message's reason for clearing the latch before the restart
> assumes that get_state calls are serialized. Does phylink actually
> guarantee that for these callers?
The C37 SGMII path needs to restart AN from xpcs_get_state() because
phylink does not call .pcs_an_restart for SGMII. On RK3568 the PCS does
not restart AN by itself, so a link that goes down would stay down
without this. The same pattern is already used by xpcs_get_state_c73(),
which calls xpcs_soft_reset() and xpcs_do_config() from the same
callback.
On the non-atomic read-act sequence: the concurrent caller can only
come from ethtool or mii_read, which do not hold pl->state_mutex. So
yes, the sequence is not strictly serialized against the normal poll
path.
The consequence is a possible extra AN restart on a link that just
came up. On this hardware an AN restart is idempotent and safe: if the
link is already up, the PCS re-negotiates and comes back up. There is
no state corruption, no register left in a bad state, and no way for
the concurrent caller to observe a torn value, since each MDIO access
is individually locked.
Before this patch a concurrent caller could clear the latch; the extra
restart is new, but its only effect is to shorten the window in which
a stale sample can be acted upon.
> [Severity: Low]
> What retries the restart if the xpcs_write() of DW_VR_MII_AN_INTR_STS
> succeeds and then this xpcs_modify() of MII_BMCR fails?
>
> The latched ANCMPLT that triggers this path has already been cleared. No
> restart is pending, and xpcs_get_state() only logs the error:
>
> phylink_resolve()
> xpcs_get_state()
> xpcs_get_state_c37_sgmii()
> xpcs_write(DW_VR_MII_AN_INTR_STS, 0) succeeds
> xpcs_modify(MII_BMCR, BMCR_ANRESTART) fails
> dev_err(..., "xpcs_get_state_c37_sgmii", ...)
>
> On parts that do not restart AN by themselves, the link would then stay
> down until the interface is reconfigured.
>
> The commit message covers this case:
>
> "If an MDIO access fails at this point, it indicates an unrecoverable
> hardware condition until reset."
>
> On RK3568, the MMIO-based MDIO back end added later in the series seems
> to fail only through pm_runtime_resume_and_get(). That failure is not
> transient. On other XPCS platforms the databook says AN restarts on its
> own. So the documented behaviour may be fine as it is.
>
> > + }
> >
> > return 0;
> > }
The latch ordering is deliberate: clearing it after the restart would
discard a freshly latched ANCMPLT from the new negotiation. An MDIO
failure at that point means the transport is gone, so there is nothing
to retry.
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
2026-10-08 17:51 [PATCH net-next v11 0/6] net: pcs: add basic support for RK3568 XPCS Coia Prant
2026-10-08 17:51 ` [PATCH net-next v11 1/6] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
@ 2026-10-08 17:51 ` Coia Prant
2026-10-09 16:49 ` Rob Herring (Arm)
` (2 more replies)
2026-10-08 17:51 ` [PATCH net-next v11 3/6] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
` (3 subsequent siblings)
5 siblings, 3 replies; 18+ messages in thread
From: Coia Prant @ 2026-10-08 17:51 UTC (permalink / raw)
To: Jakub Kicinski, netdev
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
Heiner Kallweit, Russell King, David Wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel, Coia Prant
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.
The MII port used by the SGMII link is derived from the MAC it is
routed to: GMAC0 uses port 0, GMAC1 uses port 1. The port index is
not an independent board-level configuration, so the binding does
not describe the ports. If a future SoC needs per-port configuration
that cannot be derived, pcs-handle argument cells can be added then.
The CRU reset lines (SRST_XPCS*) are intentionally not described: no
in-tree user requests them, and bring-up relies on the PD_PIPE power
domain, the SerDes PHY and the in-IP soft reset. They can be added
later as optional without breaking ABI.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../net/pcs/rockchip,rk3568-xpcs.yaml | 69 +++++++++++++++++++
1 file changed, 69 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..a331880eb11c1
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml
@@ -0,0 +1,69 @@
+# 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.
+
+properties:
+ compatible:
+ const: rockchip,rk3568-xpcs
+
+ reg:
+ maxItems: 1
+
+ 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
+
+ power-domains:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - phys
+ - 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>;
+ clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
+ clock-names = "csr", "eee";
+ phys = <&combphy2 PHY_TYPE_SGMII>;
+ power-domains = <&power RK3568_PD_PIPE>;
+ };
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
2026-10-08 17:51 ` [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
@ 2026-10-09 16:49 ` Rob Herring (Arm)
2026-10-09 17:53 ` sashiko-bot
2026-10-12 19:01 ` netdev-bot+sashiko
2 siblings, 0 replies; 18+ messages in thread
From: Rob Herring (Arm) @ 2026-10-09 16:49 UTC (permalink / raw)
To: Coia Prant
Cc: linux-arm-kernel, Maxime Coquelin, Krzysztof Kozlowski,
Paolo Abeni, Jakub Kicinski, Maxime Chevallier, linux-kernel,
Conor Dooley, netdev, devicetree, Heiner Kallweit, Andrew Lunn,
Alexandre Torgue, David S . Miller, Russell King, linux-rockchip,
Heiko Stuebner, David Wu, Eric Dumazet
On Fri, 09 Oct 2026 01:51:56 +0800, Coia Prant wrote:
> 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.
>
> The MII port used by the SGMII link is derived from the MAC it is
> routed to: GMAC0 uses port 0, GMAC1 uses port 1. The port index is
> not an independent board-level configuration, so the binding does
> not describe the ports. If a future SoC needs per-port configuration
> that cannot be derived, pcs-handle argument cells can be added then.
>
> The CRU reset lines (SRST_XPCS*) are intentionally not described: no
> in-tree user requests them, and bring-up relies on the PD_PIPE power
> domain, the SerDes PHY and the in-IP soft reset. They can be added
> later as optional without breaking ABI.
>
> Signed-off-by: Coia Prant <coiaprant@gmail.com>
> ---
> .../net/pcs/rockchip,rk3568-xpcs.yaml | 69 +++++++++++++++++++
> 1 file changed, 69 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
2026-10-08 17:51 ` [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
2026-10-09 16:49 ` Rob Herring (Arm)
@ 2026-10-09 17:53 ` sashiko-bot
2026-10-12 19:01 ` netdev-bot+sashiko
2 siblings, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-10-09 17:53 UTC (permalink / raw)
To: Coia Prant; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] net: pcs: unconditional AN restart quirk may break standard DW SGMII PHYs
- [Medium] net: pcs: xpcs: interrupt storm in SGMII mode due to uncleared AN status
--
commit 9bbe537f2904ae4cf66c0b099833bc944c6e3043
Author: Coia Prant <coiaprant@gmail.com>
dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
Add device tree binding documentation for the Synopsys DesignWare
XPCS integrated on the Rockchip RK3568 SoC.
> diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
> index b415b93d77c15..6466e0ff2a98b 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);
[Severity: Medium]
Will this unconditional AN restart quirk break standard DW SGMII PHYs?
According to the DW databook, an AN restart is not needed for MAC-side SGMII.
While explicitly documented as a workaround for a timing quirk in
Rockchip RK3568, applying it unconditionally to all compliant SGMII deployments
(e.g., NXP SJA1105) could trigger link flapping or state machine violations by
continually interrupting a link that is transitioning to an 'up' state.
[ ... ]
> @@ -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 this cause an interrupt storm in SGMII mode due to uncleared AN status?
In xpcs_get_state_c37_sgmii(), when the link successfully comes up,
DW_VR_MII_C37_ANSGM_SP_LNKSTS is true and the function returns 0 early:
drivers/net/pcs/pcs-xpcs.c:xpcs_get_state_c37_sgmii() {
...
if (ret & DW_VR_MII_C37_ANSGM_SP_LNKSTS) {
...
state->link = true;
...
return 0;
}
...
}
By returning early, the function bypasses this block that explicitly clears
DW_VR_MII_AN_INTR_STS. Since the register is not read-to-clear, the
ANCMPLT_INTR bit remains set.
If a driver operates with xpcs->pcs.poll = false (like Wangxun TXGBE) and the
interrupt is enabled, will the un-cleared status cause the MAC interrupt
handler to loop indefinitely?
> + /* Initiate the next round of AN */
> + ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,
> + BMCR_ANRESTART);
[Severity: Medium]
Does this unconditional AN restart quirk break standard DW SGMII PHYs?
Similar to xpcs_config_aneg_c37_sgmii(), this applies an explicit
Auto-Negotiation (AN) restart via BMCR_ANRESTART unconditionally when AN
completes.
Since this workaround is for Rockchip RK3568, executing it on any non-Rockchip
DW SGMII interface when AN finishes could interrupt a link that is
transitioning to an 'up' state.
> + if (ret < 0)
> + return ret;
> + }
>
> return 0;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008175225.3375274-1-coiaprant@gmail.com?part=2
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
2026-10-08 17:51 ` [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
2026-10-09 16:49 ` Rob Herring (Arm)
2026-10-09 17:53 ` sashiko-bot
@ 2026-10-12 19:01 ` netdev-bot+sashiko
2 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 19:01 UTC (permalink / raw)
To: coiaprant
Cc: kuba, netdev, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, hkallweit1, linux, david.wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The new binding
Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml…
--
dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
This adds a new device tree binding for the Synopsys DesignWare XPCS on
the Rockchip RK3568 SoC. The XPCS is accessed over APB3 and connected to a
Naneng Combo SerDes PHY. The binding covers reg, the csr and eee clocks,
one phy and one power domain, and on purpose leaves out the CRU reset
lines.
> 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..a331880eb11c1
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/pcs/rockchip,rk3568-xpcs.yaml
> @@ -0,0 +1,69 @@
[ ... ]
> + phys:
> + maxItems: 1
> +
> + power-domains:
> + maxItems: 1
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> + - phys
> + - power-domains
> +
> +additionalProperties: false
[Severity: Medium]
The properties list has no resets or interrupts entries, and
additionalProperties is false. Would a DT that fully describes this block
then fail dt_binding_check?
The SoC has four CRU reset inputs for this IP, defined in
include/dt-bindings/clock/rk3568-cru.h:
#define SRST_XPCS 150
#define SRST_XPCS_TX_DIV10 151
#define SRST_XPCS_RX_DIV10 152
#define SRST_XPCS_XGXS_RX 153
The commit message says they are left out because "no in-tree user
requests them". Documentation/devicetree/bindings/writing-bindings.rst
says the opposite:
- DO attempt to make bindings complete even if a driver doesn't support some
features. For example, if a device has an interrupt, then include the
'interrupts' property even if the driver is only polled mode.
- DON'T refer to Linux or "device driver" in bindings. Bindings should be
based on what the hardware has, not what an OS and driver currently support.
Could resets and reset-names be added now, so the binding matches the
hardware?
The commit message also says the resets "can be added later as optional
without breaking ABI". Once DTs ship without them, though, they would have
to stay optional for good.
Could a driver then ever rely on putting the TX/RX div10 and XGXS RX
domains into a known state? The in-IP soft reset may not cover those
domains.
The same question applies to interrupts. The generic binding for this IP,
snps,dw-xpcs.yaml, documents an auto-negotiation interrupt output:
interrupts:
description:
System interface interrupt output (sbd_intr_o) indicating Clause 73/37
auto-negotiation events':' Page received, AN is completed or incompatible
link partner.
maxItems: 1
Is sbd_intr_o routed to the GIC on RK3568? If it is, should this binding
have an interrupts property too? If it isn't, could the commit message say
so?
The later patches in this series leave this binding unchanged, so neither
point is addressed later in the series.
> +
> +examples:
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008175225.3375274-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v11 3/6] net: pcs: xpcs: add Rockchip RK3568 platform glue driver
2026-10-08 17:51 [PATCH net-next v11 0/6] net: pcs: add basic support for RK3568 XPCS Coia Prant
2026-10-08 17:51 ` [PATCH net-next v11 1/6] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
2026-10-08 17:51 ` [PATCH net-next v11 2/6] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
@ 2026-10-08 17:51 ` Coia Prant
2026-10-09 17:53 ` sashiko-bot
2026-10-12 19:01 ` netdev-bot+sashiko
2026-10-08 17:51 ` [PATCH net-next v11 4/6] dt-bindings: net: rockchip-dwmac: document pcs-handle Coia Prant
` (2 subsequent siblings)
5 siblings, 2 replies; 18+ messages in thread
From: Coia Prant @ 2026-10-08 17:51 UTC (permalink / raw)
To: Jakub Kicinski, netdev
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
Heiner Kallweit, Russell King, David Wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel, Coia Prant
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). On RK3568 the MII port
used by SGMII follows the MAC it is routed to: GMAC0 maps to port 0 and
GMAC1 maps to port 1. The glue driver receives the port index from the
MAC side and passes it to xpcs_create_mdiodev().
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.
PCS_XPCS_ROCKCHIP selects GENERIC_PHY and PM_GENERIC_DOMAINS.
ARCH_ROCKCHIP already selects PM, so the dependency of PM_GENERIC_DOMAINS
on PM is satisfied on the target platform.
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.
Power management
================
The XPCS, its SerDes PHY and the SATA/PCIe controllers share the PD_PIPE
power domain on RK3568. genpd powers the domain down during system
suspend once every consumer is suspended, which also kills the SerDes
and the XPCS.
Boards that rely on MAC WoL need the PCS and SerDes alive to receive
magic packets, so PD_PIPE must stay powered across system suspend. Keep
it on by marking the XPCS as part of the wakeup path via
device_set_wakeup_path(). genpd then leaves the domain powered, because
the Rockchip power domain driver sets GENPD_FLAG_ACTIVE_WAKEUP on
PD_PIPE, which makes genpd check the wakeup path of its consumers during
system suspend.
This is unconditional because the XPCS core currently has no platform
callback through which a consumer could convey the MAC WoL state, and
the state itself is dynamic: WoL is toggled at runtime via ethtool, so
a static DT property cannot express it either. The affected hardware
is limited to RK3568 boards using SGMII, which are always-on routers
where system suspend is not a realistic use case; the extra power draw
is therefore acceptable.
Runtime PM is unaffected: the runtime callbacks only gate the CSR clock,
and dev_pm_genpd_rpm_always_on() already keeps PD_PIPE on at runtime.
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 | 598 ++++++++++++++++++++++++++++++++
include/linux/pcs/pcs-xpcs-rk.h | 11 +
4 files changed, 637 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..5382afcf95748 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 if PM
+ 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..698a43b753341
--- /dev/null
+++ b/drivers/net/pcs/pcs-xpcs-rk.c
@@ -0,0 +1,598 @@
+// 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)
+{
+ 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);
+ 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);
+ 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, NULL);
+ 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. The mux only provides
+ * 125 MHz or 200 MHz, so the rate cannot drop below the 5 MHz
+ * threshold where DIV_ROUND_CLOSEST_ULL() would return 0 and the
+ * subtraction below would underflow.
+ */
+ 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 lives in the PD_PIPE power domain. 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 *pcs, int port)
+{
+ struct platform_device *pdev;
+ struct dw_xpcs_rk *pxpcs;
+ struct dw_xpcs *xpcs;
+
+ if (!of_device_is_available(pcs))
+ return ERR_PTR(-ENODEV);
+
+ if (!of_match_node(xpcs_rk_of_ids, pcs))
+ return ERR_PTR(-EINVAL);
+
+ if (!xpcs_rk_mdio_addr_validate(port))
+ return ERR_PTR(-EINVAL);
+
+ pdev = of_find_device_by_node(pcs);
+ if (!pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ /*
+ * Establish the device link before reading the supplier's drvdata.
+ * device_link_add() does not fail on a supplier that is unbinding;
+ * it creates the link in DL_STATE_SUPPLIER_UNBIND.
+ *
+ * The check below only confirms that the supplier is bound at this
+ * point. It does not fully protect against a concurrent unbind
+ * between here and xpcs_create_mdiodev(); closing that window would
+ * require reading a managed link's status or device_lock(), neither
+ * of which is acceptable on this path. The window is small and
+ * requires an explicit unbind from userspace. Discussed on
+ * driver-core.
+ *
+ * The link is released automatically when the consumer device is
+ * destroyed (DL_FLAG_AUTOREMOVE_CONSUMER), so no explicit
+ * device_link_remove() is needed on the failure paths.
+ */
+ if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) {
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ if (READ_ONCE(pdev->dev.links.status) != DL_DEV_DRIVER_BOUND) {
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ pxpcs = platform_get_drvdata(pdev);
+ if (!pxpcs || !pxpcs->bus) {
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ xpcs = xpcs_create_mdiodev(pxpcs->bus, port);
+ if (IS_ERR(xpcs)) {
+ 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 int xpcs_rk_system_suspend(struct device *dev)
+{
+ /*
+ * Keep the PD_PIPE power domain on during system suspend.
+ *
+ * PD_PIPE is shared with SATA/PCIe and would be powered down by
+ * genpd once all its consumers are suspended, killing the SerDes
+ * and breaking MAC WoL. Mark the XPCS as part of the wakeup path
+ * so genpd keeps the domain on. Unconditional because the XPCS
+ * core has no callback to convey the MAC WoL state.
+ */
+ device_set_wakeup_path(dev);
+ return 0;
+}
+
+static int xpcs_rk_system_resume(struct device *dev)
+{
+ return 0;
+}
+
+static _DEFINE_DEV_PM_OPS(xpcs_rk_pm_ops,
+ xpcs_rk_system_suspend, xpcs_rk_system_resume,
+ 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..e3ca3d51f7c7e
--- /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 *pcs, int port);
+
+#endif /* __LINUX_PCS_XPCS_ROCKCHIP_H */
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 3/6] net: pcs: xpcs: add Rockchip RK3568 platform glue driver
2026-10-08 17:51 ` [PATCH net-next v11 3/6] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
@ 2026-10-09 17:53 ` sashiko-bot
2026-10-12 19:01 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-10-09 17:53 UTC (permalink / raw)
To: Coia Prant; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] pm: PD_PIPE powers off on suspend breaking MAC WoL
- [Medium] pm: missing pm_runtime_disable() on driver unbind
- [Medium] pm: missing pm_runtime_disable() on driver unbind
- [Low] pm: unbalanced pm_runtime_enable() due to missing pm_runtime_disable()
--
commit 10f6065a84e9613f4b783ffc88353c7a176a3fcd
Author: Coia Prant <coiaprant@gmail.com>
net: pcs: xpcs: add Rockchip RK3568 platform glue driver
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.
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)
> diff --git a/drivers/net/pcs/pcs-xpcs-rk.c b/drivers/net/pcs/pcs-xpcs-rk.c
> index 0000000000000..698a43b753341
> --- /dev/null
> +++ b/drivers/net/pcs/pcs-xpcs-rk.c
[ ... ]
> +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;
[ ... ]
> + pm_runtime_set_suspended(dev);
> + pm_runtime_enable(dev);
> +
> + ret = devm_add_action_or_reset(dev, xpcs_rk_exit_clk, pxpcs);
[Severity: Medium]
Will this leave the pm runtime disable_depth unbalanced when the driver is
unbound?
The driver calls pm_runtime_enable() in xpcs_rk_init_clk(), but the teardown
action xpcs_rk_exit_clk() doesn't call pm_runtime_disable() to unwind it.
Leaving runtime PM enabled after the device is unbound breaks the runtime PM
core's tracking and can trigger an "Unbalanced pm_runtime_enable!" warning
if the device is probed again.
Should devm_pm_runtime_enable() be used, or pm_runtime_disable() be
added to the cleanup function?
[ ... ]
> +static int xpcs_rk_system_suspend(struct device *dev)
> +{
> + /*
> + * Keep the PD_PIPE power domain on during system suspend.
> + *
> + * PD_PIPE is shared with SATA/PCIe and would be powered down by
> + * genpd once all its consumers are suspended, killing the SerDes
> + * and breaking MAC WoL. Mark the XPCS as part of the wakeup path
> + * so genpd keeps the domain on. Unconditional because the XPCS
> + * core has no callback to convey the MAC WoL state.
> + */
> + device_set_wakeup_path(dev);
[Severity: Medium]
Does genpd actually keep the power domain on when device_set_wakeup_path()
is called here?
The commit message notes this relies on the Rockchip power domain driver
setting GENPD_FLAG_ACTIVE_WAKEUP on PD_PIPE. However, looking at
drivers/pmdomain/rockchip/pm-domains.c:
rk3568_pm_domains[] {
...
[RK3568_PD_PIPE] = DOMAIN_RK3568("pipe", BIT(8), BIT(11), false, false),
...
}
The wakeup parameter is false, which means GENPD_FLAG_ACTIVE_WAKEUP is not
set. Because this flag is missing, won't genpd ignore the wakeup path and
power off the domain anyway, breaking MAC WoL?
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008175225.3375274-1-coiaprant@gmail.com?part=3
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 3/6] net: pcs: xpcs: add Rockchip RK3568 platform glue driver
2026-10-08 17:51 ` [PATCH net-next v11 3/6] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
2026-10-09 17:53 ` sashiko-bot
@ 2026-10-12 19:01 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 19:01 UTC (permalink / raw)
To: coiaprant
Cc: kuba, netdev, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, hkallweit1, linux, david.wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 1 · Low: 3
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The system-suspend path does not keep PD_PIPE powered, so the
commit message's claim is false.
- [Medium] xpcs_rk_create() lets the supplier unbind while it is still
using the supplier's data (time-of-check/time-of-use).
- [Low] Concern: xpcs_rk_create() is exported but nothing in the tree
calls it.
- [Low] Concern: PCS_XPCS_ROCKCHIP uses `select PM_GENERIC_DOMAINS if PM`
and has no `depends on PM`, so with CONFIG_PM=n the driver can build…
- [Low] Concern: "rockchip,rk3568-xpcs" and its required properties have
no DT binding.
--
net: pcs: xpcs: add Rockchip RK3568 platform glue driver
This adds a platform glue driver for the Synopsys XPCS on RK3568. The
driver exposes the APB3-mapped XPCS registers as a virtual MDIO bus for
the generic pcs-xpcs core. It also moves pcs-xpcs-plat.o into its own
module behind PCS_XPCS_PLATFORM, and marks the XPCS as part of the wakeup
path during system suspend so that PD_PIPE stays powered for MAC WoL.
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)
> diff --git a/drivers/net/pcs/pcs-xpcs-rk.c b/drivers/net/pcs/pcs-xpcs-rk.c
> new file mode 100644
> index 0000000000000..698a43b753341
> --- /dev/null
> +++ b/drivers/net/pcs/pcs-xpcs-rk.c
[ ... ]
> +struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *pcs, int port)
> +{
[ ... ]
> + if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) {
> + put_device(&pdev->dev);
> + return ERR_PTR(-EPROBE_DEFER);
> + }
> +
> + if (READ_ONCE(pdev->dev.links.status) != DL_DEV_DRIVER_BOUND) {
> + put_device(&pdev->dev);
> + return ERR_PTR(-EPROBE_DEFER);
> + }
> +
> + pxpcs = platform_get_drvdata(pdev);
> + if (!pxpcs || !pxpcs->bus) {
> + put_device(&pdev->dev);
> + return ERR_PTR(-EPROBE_DEFER);
> + }
> +
> + xpcs = xpcs_create_mdiodev(pxpcs->bus, port);
[Severity: Medium]
Can the XPCS be unbound while xpcs_rk_create() is still using pxpcs and
pxpcs->bus?
The check above reads the supplier's own links.status. It does not look
at the state of the link that device_link_add() returned. Supplier unbind
is blocked only for links in DL_STATE_CONSUMER_PROBE or DL_STATE_ACTIVE:
drivers/base/core.c:device_links_busy() {
...
if (link->status == DL_STATE_CONSUMER_PROBE
|| link->status == DL_STATE_ACTIVE) {
ret = true;
...
}
Suppose the XPCS has no driver when device_link_add() runs from the GMAC
probe. device_link_init_status() then starts the link in
DL_STATE_DORMANT. If the XPCS finishes probing before the READ_ONCE()
check, device_links_driver_bound() moves the link to DL_STATE_AVAILABLE.
In that case the DL_DEV_DRIVER_BOUND check passes, but an AVAILABLE link
does not make device_links_busy() return true. A sysfs unbind of the XPCS
can then run devres_release_all() and free pxpcs and the devm mii_bus
while xpcs_rk_create() is still using them in platform_get_drvdata(),
pxpcs->bus, xpcs_create_mdiodev() and pxpcs->eee_mult_fact. This window
stays open until the GMAC probe completes.
The comment above says a small window remains. Is this AVAILABLE case the
window it means? Would checking that the returned link is in
DL_STATE_CONSUMER_PROBE, instead of checking pdev->dev.links.status,
close it?
> + if (IS_ERR(xpcs)) {
> + 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);
[Severity: Low]
This isn't a bug, but nothing in the tree calls xpcs_rk_create() at this
point in the series.
The user arrives later in "net: stmmac: dwmac-rk: add SGMII support for
RK3568". There, rk_pcs_init() calls xpcs_rk_create(priv->device,
pcs_node, bsp_priv->id) and DWMAC_ROCKCHIP selects PCS_XPCS_ROCKCHIP, so
this is resolved by the end of the series.
[ ... ]
> +static int xpcs_rk_system_suspend(struct device *dev)
> +{
> + /*
> + * Keep the PD_PIPE power domain on during system suspend.
> + *
> + * PD_PIPE is shared with SATA/PCIe and would be powered down by
> + * genpd once all its consumers are suspended, killing the SerDes
> + * and breaking MAC WoL. Mark the XPCS as part of the wakeup path
> + * so genpd keeps the domain on. Unconditional because the XPCS
> + * core has no callback to convey the MAC WoL state.
> + */
> + device_set_wakeup_path(dev);
> + return 0;
> +}
> +
> +static int xpcs_rk_system_resume(struct device *dev)
> +{
> + return 0;
> +}
[Severity: High]
Does device_set_wakeup_path() actually keep PD_PIPE powered here?
The commit message says genpd leaves the domain on "because the Rockchip
power domain driver sets GENPD_FLAG_ACTIVE_WAKEUP on PD_PIPE". However,
PD_PIPE is declared with wakeup set to false:
drivers/pmdomain/rockchip/pm-domains.c:
[RK3568_PD_PIPE] = DOMAIN_RK3568("pipe", BIT(8), BIT(11), false, false),
The flag is only added when active_wakeup is set:
drivers/pmdomain/rockchip/pm-domains.c:rockchip_pm_add_one_domain() {
...
if (pd_info->active_wakeup)
pd->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP;
...
}
genpd_finish_suspend() skips the power-off only when both conditions are
true:
drivers/pmdomain/core.c:genpd_finish_suspend() {
...
if (device_awake_path(dev) && genpd_is_active_wakeup(genpd) &&
!device_out_band_wakeup(dev))
return 0;
...
}
For PD_PIPE the XPCS is therefore counted in suspended_count. Once the
SerDes, SATA, PCIe and XPCS consumers have all suspended,
genpd_sync_power_off() turns the domain off. dev_pm_genpd_rpm_always_on()
only covers runtime PM, not this system sleep path. Nothing else in the
series changes drivers/pmdomain/.
If the domain does turn off, won't MAC WoL stop working? Won't the SerDes
and XPCS also come back at their reset defaults? xpcs_rk_system_resume()
is empty, so after resume nothing repeats phy_init()/phy_power_on() or
restores any XPCS state.
Would this need GENPD_FLAG_ACTIVE_WAKEUP set on PD_PIPE in the pmdomain
driver (or some other way to keep the domain on), plus a real resume
path?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008175225.3375274-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v11 4/6] dt-bindings: net: rockchip-dwmac: document pcs-handle
2026-10-08 17:51 [PATCH net-next v11 0/6] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (2 preceding siblings ...)
2026-10-08 17:51 ` [PATCH net-next v11 3/6] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
@ 2026-10-08 17:51 ` Coia Prant
2026-10-09 16:50 ` Rob Herring (Arm)
2026-10-12 19:01 ` netdev-bot+sashiko
2026-10-08 17:51 ` [PATCH net-next v11 5/6] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
2026-10-08 17:52 ` [PATCH net-next v11 6/6] MAINTAINERS: add entry for Rockchip XPCS driver Coia Prant
5 siblings, 2 replies; 18+ messages in thread
From: Coia Prant @ 2026-10-08 17:51 UTC (permalink / raw)
To: Jakub Kicinski, netdev
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
Heiner Kallweit, Russell King, David Wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel, Coia Prant
The Rockchip GMAC binding needs to describe the PCS reference used by
the SGMII support added later in this series. The property will be
parsed by rk_pcs_init(), and a missing phandle fails the probe. Add it
and require it when phy-mode is "sgmii" on rockchip,rk3568-gmac, the
only SoC in this binding that has SGMII support.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../bindings/net/rockchip-dwmac.yaml | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
index 80c252845349c..c434450fd07ff 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,27 @@ allOf:
clocks:
minItems: 5
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: rockchip,rk3568-gmac
+ then:
+ required:
+ - phy-mode
+
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: rockchip,rk3568-gmac
+ phy-mode:
+ contains:
+ const: sgmii
+ then:
+ required:
+ - pcs-handle
+
unevaluatedProperties: false
examples:
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 4/6] dt-bindings: net: rockchip-dwmac: document pcs-handle
2026-10-08 17:51 ` [PATCH net-next v11 4/6] dt-bindings: net: rockchip-dwmac: document pcs-handle Coia Prant
@ 2026-10-09 16:50 ` Rob Herring (Arm)
2026-10-12 19:01 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: Rob Herring (Arm) @ 2026-10-09 16:50 UTC (permalink / raw)
To: Coia Prant
Cc: Maxime Coquelin, netdev, Conor Dooley, Andrew Lunn, linux-kernel,
Heiko Stuebner, Krzysztof Kozlowski, Jakub Kicinski,
linux-rockchip, devicetree, linux-arm-kernel, Eric Dumazet,
Russell King, Alexandre Torgue, Paolo Abeni, David Wu,
Heiner Kallweit, Maxime Chevallier, David S . Miller
On Fri, 09 Oct 2026 01:51:58 +0800, Coia Prant wrote:
> The Rockchip GMAC binding needs to describe the PCS reference used by
> the SGMII support added later in this series. The property will be
> parsed by rk_pcs_init(), and a missing phandle fails the probe. Add it
> and require it when phy-mode is "sgmii" on rockchip,rk3568-gmac, the
> only SoC in this binding that has SGMII support.
>
> Signed-off-by: Coia Prant <coiaprant@gmail.com>
> ---
> .../bindings/net/rockchip-dwmac.yaml | 27 +++++++++++++++++++
> 1 file changed, 27 insertions(+)
>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net-next v11 4/6] dt-bindings: net: rockchip-dwmac: document pcs-handle
2026-10-08 17:51 ` [PATCH net-next v11 4/6] dt-bindings: net: rockchip-dwmac: document pcs-handle Coia Prant
2026-10-09 16:50 ` Rob Herring (Arm)
@ 2026-10-12 19:01 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 19:01 UTC (permalink / raw)
To: coiaprant
Cc: kuba, netdev, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, hkallweit1, linux, david.wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] The patch adds an allOf entry to
Documentation/devicetree/bindings/net/rockchip-dwmac.yaml (lines…
- [Low] At this commit, rockchip-dwmac.yaml documents `pcs-handle` and
requires it for rk3568 when phy-mode is "sgmii", but nothing in dwmac-…
--
dt-bindings: net: rockchip-dwmac: document pcs-handle
This patch documents a pcs-handle property in the Rockchip GMAC binding.
It makes the property required for rockchip,rk3568-gmac when phy-mode is
"sgmii". The SGMII driver support that uses the property arrives later in
the series.
> diff --git a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
> index 80c252845349c..c434450fd07ff 100644
> --- a/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
> +++ b/Documentation/devicetree/bindings/net/rockchip-dwmac.yaml
[ ... ]
> @@ -159,6 +165,27 @@ allOf:
> clocks:
> minItems: 5
>
> + - if:
> + properties:
> + compatible:
> + contains:
> + const: rockchip,rk3568-gmac
> + then:
> + required:
> + - phy-mode
> +
[Severity: Low]
Is this block needed? The commit message doesn't mention it.
rockchip-dwmac.yaml already includes snps,dwmac.yaml through its allOf:
- $ref: snps,dwmac.yaml#
That schema already requires phy-mode for every node, with no condition:
Documentation/devicetree/bindings/net/snps,dwmac.yaml:
required:
- compatible
- reg
- interrupts
- interrupt-names
- phy-mode
So phy-mode is already required for all Rockchip GMAC compatibles,
including rk3568, and dt_binding_check / dtbs_check give the same results
with or without this block.
Repeating the requirement for rk3568 only could also suggest that phy-mode
is optional on the other Rockchip SoCs.
If the goal is to keep the sgmii if clause below from matching when
phy-mode is absent, the inherited requirement already does that. Could
this block be dropped? Alternatively, a local required: [phy-mode] could
go inside the sgmii if clause.
> + - if:
> + properties:
> + compatible:
> + contains:
> + const: rockchip,rk3568-gmac
> + phy-mode:
> + contains:
> + const: sgmii
> + then:
> + required:
> + - pcs-handle
> +
[Severity: Low]
At this commit, nothing in dwmac-rk.c reads pcs-handle yet, and the driver
doesn't accept SGMII. Doesn't that leave the binding and the driver out of
sync if someone bisects to this commit or applies only part of the series?
The consumer arrives in the next patch, "net: stmmac: dwmac-rk: add SGMII
support for RK3568". That patch adds rk_pcs_init():
drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c:rk_pcs_init() {
...
pcs_node = of_parse_phandle(node, "pcs-handle", 0);
if (!pcs_node)
return -ENODEV;
...
}
It also adds the SGMII pcs_init/pcs_exit/select_pcs handling to
rk_gmac_probe(). By the end of the series the binding and driver match,
and putting the binding before its user is the usual DT submission order.
I'm only noting it here for completeness.
> unevaluatedProperties: false
>
> examples:
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008175225.3375274-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v11 5/6] net: stmmac: dwmac-rk: add SGMII support for RK3568
2026-10-08 17:51 [PATCH net-next v11 0/6] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (3 preceding siblings ...)
2026-10-08 17:51 ` [PATCH net-next v11 4/6] dt-bindings: net: rockchip-dwmac: document pcs-handle Coia Prant
@ 2026-10-08 17:51 ` Coia Prant
2026-10-09 17:53 ` sashiko-bot
2026-10-12 19:01 ` netdev-bot+sashiko
2026-10-08 17:52 ` [PATCH net-next v11 6/6] MAINTAINERS: add entry for Rockchip XPCS driver Coia Prant
5 siblings, 2 replies; 18+ messages in thread
From: Coia Prant @ 2026-10-08 17:51 UTC (permalink / raw)
To: Jakub Kicinski, netdev
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
Heiner Kallweit, Russell King, David Wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel, Coia Prant
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. The MII port passed
to xpcs_rk_create() is the GMAC id (bsp_priv->id): GMAC0 maps to port 0,
GMAC1 maps to port 1, matching the hardware routing.
SGMII is not added to rk_get_interfaces(): it comes from the XPCS's own
supported_interfaces, merged by stmmac_phylink_setup().
The SerDes PHY and the PD_PIPE power domain are owned by the XPCS
driver rather than managed through the stmmac
serdes_poweron/serdes_poweroff callbacks, which are legacy and meant
for single-MAC platforms. On RK3568 the XPCS is the natural owner of
the shared SerDes.
DWMAC_ROCKCHIP selects PCS_XPCS_ROCKCHIP. PCS_XPCS itself is already
selected by STMMAC_ETH, and PM is selected by ARCH_ROCKCHIP, so no
further selects are needed.
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 changed per
speed. Out-of-band mode requires the MAC to change its clock to
125/25/2.5 MHz for 1000/100/10 Mbps respectively, which the hardware
does not support here. The driver therefore clears rk_set_clk_tx_rate()
and the clock stays at 125 MHz.
The PCS is still told the speed in out-of-band mode (through a BMCR
write in xpcs_link_up_sgmii_1000basex()), but the MAC clock does not
follow. At 10/100 Mbps this gives working TX and failing RX.
Enable default_an_inband for SGMII and clear rk_set_clk_tx_rate(), so
phylink uses 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 only out-of-band configuration that would work is 1000BASE-X with a
fixed-link, where the MAC clock stays at 125 MHz; 10/100 Mbps gives
working TX and failing RX. Boards must use in-band mode
(managed = "in-band-status" or an in-band capable PHY).
1000BASE-X is not enabled by this series; it may be added later if
there is a user for it.
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 | 131 +++++++++++++++---
2 files changed, 112 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 72bdbcb5e863a..c4db919ec1640 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 */
@@ -1211,6 +1239,44 @@ 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 rk_priv_data *bsp_priv = priv->plat->bsp_priv;
+ struct device_node *node = priv->device->of_node;
+ struct device_node *pcs_node;
+ struct dw_xpcs *xpcs;
+
+ pcs_node = of_parse_phandle(node, "pcs-handle", 0);
+ if (!pcs_node)
+ return -ENODEV;
+
+ xpcs = xpcs_rk_create(priv->device, pcs_node, bsp_priv->id);
+ 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)
@@ -1333,6 +1399,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);
@@ -1364,6 +1431,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);
@@ -1382,16 +1453,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 */
@@ -1402,10 +1476,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) {
@@ -1413,13 +1485,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");
@@ -1450,15 +1521,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);
@@ -1466,6 +1542,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)
@@ -1605,6 +1685,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 related [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 5/6] net: stmmac: dwmac-rk: add SGMII support for RK3568
2026-10-08 17:51 ` [PATCH net-next v11 5/6] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
@ 2026-10-09 17:53 ` sashiko-bot
2026-10-12 19:01 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: sashiko-bot @ 2026-10-09 17:53 UTC (permalink / raw)
To: Coia Prant; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] net: stmmac: dw_xpcs memory leak and overwrite in stmmac_pcs_setup
--
commit de93d801ba1c997500b23cca54a77392e6107f28
Author: Coia Prant <coiaprant@gmail.com>
net: stmmac: dwmac-rk: add SGMII support for RK3568
The RK3568 SoC integrates a Synopsys DesignWare XPCS that can be connected
to GMAC0 or GMAC1 in SGMII mode. This commit adds the necessary glue logic
to support this configuration in the dwmac-rk driver.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 386)
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
[ ... ]
> @@ -1211,6 +1239,44 @@ static void rk_phy_powerdown(struct rk_priv_data *bsp_priv)
[ ... ]
> +static int rk_pcs_init(struct stmmac_priv *priv)
> +{
> + struct rk_priv_data *bsp_priv = priv->plat->bsp_priv;
> + struct device_node *node = priv->device->of_node;
> + struct device_node *pcs_node;
> + struct dw_xpcs *xpcs;
> +
> + pcs_node = of_parse_phandle(node, "pcs-handle", 0);
> + if (!pcs_node)
> + return -ENODEV;
> +
> + xpcs = xpcs_rk_create(priv->device, pcs_node, bsp_priv->id);
> + of_node_put(pcs_node);
> + if (IS_ERR(xpcs))
> + return PTR_ERR(xpcs);
> +
> + priv->hw->xpcs = xpcs;
> + return 0;
> +}
[Severity: High]
Does this code leak the newly allocated XPCS instance when interacting with
stmmac_pcs_setup() in the core driver?
Looking at stmmac_pcs_setup() in drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c,
it declares a local xpcs variable initialized to NULL:
int stmmac_pcs_setup(struct net_device *ndev)
{
struct dw_xpcs *xpcs = NULL;
...
if (priv->plat->pcs_init) {
ret = priv->plat->pcs_init(priv);
...
priv->hw->xpcs = xpcs;
return 0;
}
Because rk_pcs_init() successfully allocates the XPCS and assigns it directly
to priv->hw->xpcs, it returns 0.
The local variable xpcs in stmmac_pcs_setup() remains NULL. At the end of
stmmac_pcs_setup(), it unconditionally overwrites the newly allocated pointer
in priv->hw->xpcs with NULL.
Will this cause the MAC to lose its PCS reference and break SGMII link
negotiation?
Additionally, does this leak the dw_xpcs object memory, since teardown
functions like rk_pcs_exit() will see a NULL pointer and exit early?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261008175225.3375274-1-coiaprant@gmail.com?part=5
^ permalink raw reply [flat|nested] 18+ messages in thread* Re: [PATCH net-next v11 5/6] net: stmmac: dwmac-rk: add SGMII support for RK3568
2026-10-08 17:51 ` [PATCH net-next v11 5/6] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
2026-10-09 17:53 ` sashiko-bot
@ 2026-10-12 19:01 ` netdev-bot+sashiko
1 sibling, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-12 19:01 UTC (permalink / raw)
To: coiaprant
Cc: kuba, netdev, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, maxime.chevallier, mcoquelin.stm32,
alexandre.torgue, hkallweit1, linux, david.wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 3 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit message says "1000BASE-X is not enabled by this
series" and that SGMII "comes from the XPCS's own…
- [Medium] On an SGMII-configured RK3568, rk_get_interfaces() still
advertises RMII and all four RGMII variants: supports_rgmii comes from…
- [Medium] For RK3568 SGMII, rk_gmac_probe() sets
`plat_dat->set_clk_tx_rate = NULL` and only sets `default_an_inband =…
--
net: stmmac: dwmac-rk: add SGMII support for RK3568
This patch adds SGMII support to the RK3568 dwmac glue. It routes the GMAC
to the integrated Synopsys XPCS through a GRF bit, creates the XPCS with
xpcs_rk_create() in new pcs_init/pcs_exit callbacks, and returns it from
select_pcs. It also moves the clock enable earlier in rk_gmac_powerup(),
and for SGMII it forces in-band negotiation with a fixed 125 MHz MAC clock.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 386)
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
> index 72bdbcb5e863a..c4db919ec1640 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
[ ... ]
> +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);
> +}
[Severity: Medium]
rk_select_pcs() ignores its interface argument and returns the XPCS for
every mode. Is this statement in the commit message accurate?
"1000BASE-X is not enabled by this series"
rk_pcs_init() creates a generic DW XPCS through xpcs_rk_create(). Then
xpcs_create() fills the PCS supported_interfaces from the matched compat
table:
drivers/net/pcs/pcs-xpcs.c:xpcs_get_interfaces() {
...
for (compat = xpcs->desc->compat; compat->supported; compat++)
__set_bit(compat->interface, interfaces);
...
}
For a Synopsys ID, synopsys_xpcs_compat[] lists SGMII, 1000BASEX,
2500BASEX, USXGMII and the 10G and faster modes. stmmac_phylink_setup()
then merges all of them into phylink's supported set:
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_phylink_setup() {
...
if (pcs)
phy_interface_or(config->supported_interfaces,
config->supported_interfaces,
pcs->supported_interfaces);
...
}
The 1G MAC capabilities filter out the faster modes, but 1000BASE-X still
passes validation. Phylink can select it at runtime, for example through
phylink_sfp_config_optical() for a fibre SFP, or for a PHY that switches
its host interface.
rk_gmac_check_ops() and rk_gmac_powerup() only check the static DT
phy_iface, so the glue never validates or configures this mode.
Is there something meant to stop phylink from choosing 1000BASE-X on this
port, so that the SGMII-only behaviour in the commit message is actually
enforced?
[Severity: Medium]
On an RK3568 configured for SGMII, rk_get_interfaces() still advertises
RMII and the RGMII variants. supports_rgmii comes from set_to_rgmii, and
supports_rmii now comes from the new rk3568_set_to_rmii. rk_select_pcs()
returns the XPCS for those parallel modes as well.
What happens when phylink validates the whole supported_interfaces mask?
One example is phylink_validate() with PHY_INTERFACE_MODE_NA, which
phylink_validate_phy() uses for a C45 PHY without rate matching or
possible_interfaces:
drivers/net/phy/phylink.c:phylink_validate_mac_and_pcs() {
...
if (!phy_interface_empty(pcs->supported_interfaces) &&
!test_bit(state->interface, pcs->supported_interfaces)) {
phylink_err(pl, "MAC returned PCS which does not support %s\n",
...
}
Would this log an error and return -EINVAL for each RGMII and RMII mode on
that port? Should rk_select_pcs() return NULL for interfaces the XPCS does
not support?
[ ... ]
> @@ -1605,6 +1685,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;
[Severity: Medium]
default_an_inband only sets a default here. With set_clk_tx_rate cleared,
does anything prevent out-of-band operation at 10/100 Mb/s?
A fixed-link node overrides default_an_inband in phylink_parse_mode().
Phylink can also fall back to out-of-band when the PHY reports only
LINK_INBAND_DISABLE, because the XPCS allows that:
drivers/net/pcs/pcs-xpcs.c:xpcs_inband_caps() {
...
case DW_AN_C37_SGMII:
case DW_AN_C37_1000BASEX:
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
...
}
In that case stmmac_mac_link_up() sets the MAC speed bits and
xpcs_link_up_sgmii_1000basex() writes the speed into BMCR, but the MAC
clock stays at 125 MHz. The commit message says this "gives working TX and
failing RX" and that "Boards must use in-band mode".
The driver does not reject out-of-band mode, does not limit the MAC
capabilities to 1000 Mb/s in that mode, and does not log anything. Can the
link then come up at 10/100 with a broken receive path and no warning?
> + plat_dat->pcs_init = rk_pcs_init;
> + plat_dat->pcs_exit = rk_pcs_exit;
> + plat_dat->select_pcs = rk_select_pcs;
> + }
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261008175225.3375274-1-coiaprant%40gmail.com
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net-next v11 6/6] MAINTAINERS: add entry for Rockchip XPCS driver
2026-10-08 17:51 [PATCH net-next v11 0/6] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (4 preceding siblings ...)
2026-10-08 17:51 ` [PATCH net-next v11 5/6] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
@ 2026-10-08 17:52 ` Coia Prant
5 siblings, 0 replies; 18+ messages in thread
From: Coia Prant @ 2026-10-08 17:52 UTC (permalink / raw)
To: Jakub Kicinski, netdev
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
Heiner Kallweit, Russell King, David Wu, linux-rockchip,
devicetree, linux-arm-kernel, linux-kernel, Coia Prant
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 65e8a4b5c90b1..2f0f46eabcf54 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23757,6 +23757,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 related [flat|nested] 18+ messages in thread