* [PATCH net-next v4 01/10] net: stmmac: move XPCS lifetime management to platform drivers
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:13 ` Jakub Kicinski
2026-09-07 15:37 ` [PATCH net-next v4 02/10] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property Coia Prant
` (9 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The current XPCS creation logic in stmmac_pcs_setup() is problematic
for several reasons.
First, if a device tree specifies a "pcs-handle" but no select_pcs()
callback is provided by the platform driver, the created XPCS is never
used. The phylink framework requires select_pcs() to actually return
the PCS to the core, so the pcs-handle property becomes effectively
useless without the matching callback. This is confusing for developers
who expect that specifying a pcs-handle in their device tree should be
sufficient to enable the PCS.
Second, and more critically, when stmmac_pcs_setup() fails to create
an XPCS (either because no pcs-handle is present and no pcs_mask is
configured), it falls through to the else branch and leaves
priv->hw->xpcs as NULL. This will silently override any XPCS that a
platform driver may have already set up during its own initialization,
for example in a pcs_init() callback or during probe. The platform
driver has no way to prevent this override because the common code
runs unconditionally after the platform-specific initialization.
After commit 93f84152e4ae ("net: stmmac: clean up
stmmac_mac_select_pcs()"), the common code no longer falls back to
priv->hw->phylink_pcs if select_pcs() is not set. This change
reinforces that each platform must manage its own PCS life cycle
explicitly, but the XPCS creation code in stmmac_pcs_setup() was not
updated to match this new expectation, leaving a gap where platform
drivers have no clean way to take control of XPCS creation.
Address all of these issues by introducing pcs_init() and pcs_exit()
callbacks in plat_stmmacenet_data. These callbacks give platform
drivers full control over when and how the XPCS is created, configured,
and destroyed. The common stmmac_pcs_setup() and stmmac_pcs_clean()
functions are simplified to just call these callbacks, removing the
confusing and error-prone XPCS creation logic from the common code.
Platforms that do not need an XPCS simply leave the callbacks as NULL
and no change in behavior occurs. Platforms that do need an XPCS can
now create it with the exact configuration they require, including
wrapping it with custom phylink_pcs_ops when necessary.
Existing platform drivers (intel, rzn1, socfpga) are updated to use
the new callbacks by moving their XPCS creation and cleanup logic into
pcs_init() and pcs_exit(). In their pcs_exit() implementations, the
pointer to the destroyed PCS is explicitly set to NULL to avoid
dangling pointer references.
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../net/ethernet/stmicro/stmmac/dwmac-intel.c | 44 +++++++++++++++++--
.../stmicro/stmmac/dwmac-renesas-gbeth.c | 7 ++-
.../net/ethernet/stmicro/stmmac/dwmac-rzn1.c | 7 ++-
.../ethernet/stmicro/stmmac/dwmac-socfpga.c | 7 ++-
.../net/ethernet/stmicro/stmmac/stmmac_mdio.c | 37 +++-------------
5 files changed, 61 insertions(+), 41 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
index f5f9fa67ecd77..fd5f01c8941c1 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
@@ -603,13 +603,47 @@ static void common_default_data(struct plat_stmmacenet_data *plat)
plat->mdio_bus_data->needs_reset = true;
}
+static int intel_mgbe_pcs_init(struct stmmac_priv *priv)
+{
+ struct fwnode_handle *devnode, *pcsnode;
+ struct dw_xpcs *xpcs = NULL;
+ int addr;
+
+ devnode = dev_fwnode(priv->device);
+
+ if (fwnode_property_present(devnode, "pcs-handle")) {
+ pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
+ xpcs = xpcs_create_fwnode(pcsnode);
+ fwnode_handle_put(pcsnode);
+ } else {
+ addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
+ xpcs = xpcs_create_mdiodev(priv->mii, addr);
+ }
+
+ if (IS_ERR(xpcs))
+ return PTR_ERR(xpcs);
+
+ xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
+
+ priv->hw->xpcs = xpcs;
+ return 0;
+}
+
+static void intel_mgbe_pcs_exit(struct stmmac_priv *priv)
+{
+ if (!priv->hw->xpcs)
+ return;
+
+ xpcs_destroy(priv->hw->xpcs);
+ priv->hw->xpcs = NULL;
+}
+
static struct phylink_pcs *intel_mgbe_select_pcs(struct stmmac_priv *priv,
phy_interface_t interface)
{
- /* plat->mdio_bus_data->has_xpcs has been set true, so there
- * should always be an XPCS. The original code would always
- * return this if present.
- */
+ if (!priv->hw->xpcs)
+ return NULL;
+
return xpcs_to_phylink_pcs(priv->hw->xpcs);
}
@@ -733,6 +767,8 @@ static int intel_mgbe_common_data(struct pci_dev *pdev,
plat->phy_interface == PHY_INTERFACE_MODE_1000BASEX) {
plat->mdio_bus_data->pcs_mask = BIT_U32(INTEL_MGBE_XPCS_ADDR);
plat->default_an_inband = true;
+ plat->pcs_init = intel_mgbe_pcs_init;
+ plat->pcs_exit = intel_mgbe_pcs_exit;
plat->select_pcs = intel_mgbe_select_pcs;
}
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
index 19f34e18bfef2..9af32c26f9c14 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
@@ -81,8 +81,11 @@ static int renesas_gmac_pcs_init(struct stmmac_priv *priv)
static void renesas_gmac_pcs_exit(struct stmmac_priv *priv)
{
- if (priv->hw->phylink_pcs)
- miic_destroy(priv->hw->phylink_pcs);
+ if (!priv->hw->phylink_pcs)
+ return;
+
+ miic_destroy(priv->hw->phylink_pcs);
+ priv->hw->phylink_pcs = NULL;
}
static struct phylink_pcs *renesas_gmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
index 13634965bc19a..01df4776edb3f 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
@@ -35,8 +35,11 @@ static int rzn1_dwmac_pcs_init(struct stmmac_priv *priv)
static void rzn1_dwmac_pcs_exit(struct stmmac_priv *priv)
{
- if (priv->hw->phylink_pcs)
- miic_destroy(priv->hw->phylink_pcs);
+ if (!priv->hw->phylink_pcs)
+ return;
+
+ miic_destroy(priv->hw->phylink_pcs);
+ priv->hw->phylink_pcs = NULL;
}
static struct phylink_pcs *rzn1_dwmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
index 1d7f0a57d2889..6d4bc1fe8f751 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
@@ -539,8 +539,11 @@ static int socfpga_dwmac_pcs_init(struct stmmac_priv *priv)
static void socfpga_dwmac_pcs_exit(struct stmmac_priv *priv)
{
- if (priv->hw->phylink_pcs)
- lynx_pcs_destroy(priv->hw->phylink_pcs);
+ if (!priv->hw->phylink_pcs)
+ return;
+
+ lynx_pcs_destroy(priv->hw->phylink_pcs);
+ priv->hw->phylink_pcs = NULL;
}
static struct phylink_pcs *socfpga_dwmac_select_pcs(struct stmmac_priv *priv,
diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
index afe98ff5bdcb0..d2f77f0c223a7 100644
--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
+++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
@@ -426,36 +426,15 @@ int stmmac_mdio_reset(struct mii_bus *bus)
int stmmac_pcs_setup(struct net_device *ndev)
{
struct stmmac_priv *priv = netdev_priv(ndev);
- struct fwnode_handle *devnode, *pcsnode;
- struct dw_xpcs *xpcs = NULL;
- int addr, ret;
-
- devnode = dev_fwnode(priv->device);
-
- if (priv->plat->pcs_init) {
- ret = priv->plat->pcs_init(priv);
- } else if (fwnode_property_present(devnode, "pcs-handle")) {
- pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
- xpcs = xpcs_create_fwnode(pcsnode);
- fwnode_handle_put(pcsnode);
- ret = PTR_ERR_OR_ZERO(xpcs);
- } else if (priv->plat->mdio_bus_data &&
- priv->plat->mdio_bus_data->pcs_mask) {
- addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
- xpcs = xpcs_create_mdiodev(priv->mii, addr);
- ret = PTR_ERR_OR_ZERO(xpcs);
- } else {
+ int ret;
+
+ if (!priv->plat->pcs_init)
return 0;
- }
+ ret = priv->plat->pcs_init(priv);
if (ret)
return dev_err_probe(priv->device, ret, "No xPCS found\n");
- if (xpcs)
- xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
-
- priv->hw->xpcs = xpcs;
-
return 0;
}
@@ -463,14 +442,10 @@ void stmmac_pcs_clean(struct net_device *ndev)
{
struct stmmac_priv *priv = netdev_priv(ndev);
- if (priv->plat->pcs_exit)
- priv->plat->pcs_exit(priv);
-
- if (!priv->hw->xpcs)
+ if (!priv->plat->pcs_exit)
return;
- xpcs_destroy(priv->hw->xpcs);
- priv->hw->xpcs = NULL;
+ priv->plat->pcs_exit(priv);
}
struct stmmac_clk_rate {
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 01/10] net: stmmac: move XPCS lifetime management to platform drivers
2026-09-07 15:37 ` [PATCH net-next v4 01/10] net: stmmac: move XPCS lifetime management to platform drivers Coia Prant
@ 2026-09-12 20:13 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:13 UTC (permalink / raw)
To: coiaprant
Cc: Jakub Kicinski, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, vkoul, maxime.chevallier,
mcoquelin.stm32, alexandre.torgue, prabhakar.mahadev-lad.rj,
romain.gantois, hkallweit1, neil.armstrong, linux, shawn.lin,
david, netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: stmmac: move XPCS lifetime management to platform drivers
This removes the generic XPCS creation and destruction code from
stmmac_pcs_setup() and stmmac_pcs_clean(), leaving those functions as
plain dispatchers to plat->pcs_init() and plat->pcs_exit(). The Intel
mGbE glue gains its own pcs_init()/pcs_exit() implementations, and the
renesas-gbeth, rzn1 and socfpga pcs_exit() callbacks now clear
priv->hw->phylink_pcs after destroying the PCS.
A few questions on the changelog and on the removed common code below.
[Severity: Medium]
Three statements in the commit message do not seem to match the tree
this patch applies to.
First:
Address all of these issues by introducing pcs_init() and pcs_exit()
callbacks in plat_stmmacenet_data.
Are these callbacks actually introduced here? Both function pointers
already exist in struct plat_stmmacenet_data at the baseline:
int (*pcs_init)(struct stmmac_priv *priv);
void (*pcs_exit)(struct stmmac_priv *priv);
and this patch does not touch include/linux/stmmac.h at all. The
baseline stmmac_pcs_setup() and stmmac_pcs_clean() already call them.
Second:
Second, and more critically, when stmmac_pcs_setup() fails to create
an XPCS (either because no pcs-handle is present and no pcs_mask is
configured), it falls through to the else branch and leaves
priv->hw->xpcs as NULL. This will silently override any XPCS that a
platform driver may have already set up
Is that the branch that causes the override? In the baseline that arm
returns immediately:
drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c:stmmac_pcs_setup() {
if (priv->plat->pcs_init) {
ret = priv->plat->pcs_init(priv);
} else if (fwnode_property_present(devnode, "pcs-handle")) {
...
} else {
return 0;
}
...
priv->hw->xpcs = xpcs;
}
so no XPCS pointer can be clobbered there. The clobber only happens on
the plat->pcs_init branch, where the local xpcs stays NULL and the
unconditional priv->hw->xpcs = xpcs; afterwards overwrites whatever
pcs_init() stored. Could the changelog describe that path instead?
Third:
Existing platform drivers (intel, rzn1, socfpga) are updated to use
the new callbacks by moving their XPCS creation and cleanup logic into
pcs_init() and pcs_exit().
rzn1 and socfpga already used pcs_init()/pcs_exit() before this patch,
and neither creates an XPCS: rzn1_dwmac_pcs_init() calls miic_create()
and socfpga_dwmac_pcs_init() calls lynx_pcs_create_mdiodev(). The only
change for them is the added NULL assignment. The patch also modifies
dwmac-renesas-gbeth.c, which the changelog does not mention. Should
that list be corrected to intel only, plus a note about the pointer
clearing in renesas-gbeth, rzn1 and socfpga?
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
> index f5f9fa67ecd77..fd5f01c8941c1 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-intel.c
> @@ -603,13 +603,47 @@ static void common_default_data(struct plat_stmmacenet_data *plat)
> plat->mdio_bus_data->needs_reset = true;
> }
>
> +static int intel_mgbe_pcs_init(struct stmmac_priv *priv)
> +{
> + struct fwnode_handle *devnode, *pcsnode;
> + struct dw_xpcs *xpcs = NULL;
[Severity: Low]
This isn't a bug, but is the NULL initializer still reachable here?
Both arms of the if/else below assign xpcs unconditionally, from
xpcs_create_fwnode() or xpcs_create_mdiodev(), and neither returns NULL.
In the common code this was moved from, the initializer mattered because
a fall-through left xpcs NULL and the call was guarded by
if (xpcs) xpcs_config_eee_mult_fact(...); that guard is now gone.
> + int addr;
> +
> + devnode = dev_fwnode(priv->device);
> +
> + if (fwnode_property_present(devnode, "pcs-handle")) {
> + pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
> + xpcs = xpcs_create_fwnode(pcsnode);
> + fwnode_handle_put(pcsnode);
> + } else {
> + addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
> + xpcs = xpcs_create_mdiodev(priv->mii, addr);
> + }
> +
> + if (IS_ERR(xpcs))
> + return PTR_ERR(xpcs);
> +
> + xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
> +
> + priv->hw->xpcs = xpcs;
> + return 0;
> +}
> +
> +static void intel_mgbe_pcs_exit(struct stmmac_priv *priv)
> +{
> + if (!priv->hw->xpcs)
> + return;
> +
> + xpcs_destroy(priv->hw->xpcs);
> + priv->hw->xpcs = NULL;
> +}
> +
[ ... ]
> @@ -733,6 +767,8 @@ static int intel_mgbe_common_data(struct pci_dev *pdev,
> plat->phy_interface == PHY_INTERFACE_MODE_1000BASEX) {
> plat->mdio_bus_data->pcs_mask = BIT_U32(INTEL_MGBE_XPCS_ADDR);
> plat->default_an_inband = true;
> + plat->pcs_init = intel_mgbe_pcs_init;
> + plat->pcs_exit = intel_mgbe_pcs_exit;
> plat->select_pcs = intel_mgbe_select_pcs;
> }
>
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
> index 19f34e18bfef2..9af32c26f9c14 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-renesas-gbeth.c
> @@ -81,8 +81,11 @@ static int renesas_gmac_pcs_init(struct stmmac_priv *priv)
>
> static void renesas_gmac_pcs_exit(struct stmmac_priv *priv)
> {
> - if (priv->hw->phylink_pcs)
> - miic_destroy(priv->hw->phylink_pcs);
> + if (!priv->hw->phylink_pcs)
> + return;
> +
> + miic_destroy(priv->hw->phylink_pcs);
> + priv->hw->phylink_pcs = NULL;
> }
>
> static struct phylink_pcs *renesas_gmac_select_pcs(struct stmmac_priv *priv,
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
> index 13634965bc19a..01df4776edb3f 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rzn1.c
> @@ -35,8 +35,11 @@ static int rzn1_dwmac_pcs_init(struct stmmac_priv *priv)
>
> static void rzn1_dwmac_pcs_exit(struct stmmac_priv *priv)
> {
> - if (priv->hw->phylink_pcs)
> - miic_destroy(priv->hw->phylink_pcs);
> + if (!priv->hw->phylink_pcs)
> + return;
> +
> + miic_destroy(priv->hw->phylink_pcs);
> + priv->hw->phylink_pcs = NULL;
> }
>
> static struct phylink_pcs *rzn1_dwmac_select_pcs(struct stmmac_priv *priv,
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
> index 1d7f0a57d2889..6d4bc1fe8f751 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-socfpga.c
> @@ -539,8 +539,11 @@ static int socfpga_dwmac_pcs_init(struct stmmac_priv *priv)
>
> static void socfpga_dwmac_pcs_exit(struct stmmac_priv *priv)
> {
> - if (priv->hw->phylink_pcs)
> - lynx_pcs_destroy(priv->hw->phylink_pcs);
> + if (!priv->hw->phylink_pcs)
> + return;
> +
> + lynx_pcs_destroy(priv->hw->phylink_pcs);
> + priv->hw->phylink_pcs = NULL;
> }
>
> static struct phylink_pcs *socfpga_dwmac_select_pcs(struct stmmac_priv *priv,
> diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
> index afe98ff5bdcb0..d2f77f0c223a7 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c
> @@ -426,36 +426,15 @@ int stmmac_mdio_reset(struct mii_bus *bus)
> int stmmac_pcs_setup(struct net_device *ndev)
> {
> struct stmmac_priv *priv = netdev_priv(ndev);
> - struct fwnode_handle *devnode, *pcsnode;
> - struct dw_xpcs *xpcs = NULL;
> - int addr, ret;
> -
> - devnode = dev_fwnode(priv->device);
> -
> - if (priv->plat->pcs_init) {
> - ret = priv->plat->pcs_init(priv);
> - } else if (fwnode_property_present(devnode, "pcs-handle")) {
> - pcsnode = fwnode_find_reference(devnode, "pcs-handle", 0);
> - xpcs = xpcs_create_fwnode(pcsnode);
> - fwnode_handle_put(pcsnode);
> - ret = PTR_ERR_OR_ZERO(xpcs);
> - } else if (priv->plat->mdio_bus_data &&
> - priv->plat->mdio_bus_data->pcs_mask) {
> - addr = ffs(priv->plat->mdio_bus_data->pcs_mask) - 1;
> - xpcs = xpcs_create_mdiodev(priv->mii, addr);
> - ret = PTR_ERR_OR_ZERO(xpcs);
> - } else {
> + int ret;
> +
> + if (!priv->plat->pcs_init)
> return 0;
> - }
[Severity: Medium]
Does this drop the generic handling of the "pcs-handle" property? After
this change, any snps,dwmac node whose firmware points "pcs-handle" at a
Synopsys XPCS, but whose glue driver does not set plat->pcs_init, gets
priv->hw->xpcs == NULL and no message at all, since the function just
returns 0. "pcs-handle" is still a documented generic property in
Documentation/devicetree/bindings/net/ethernet-controller.yaml.
The changelog explains this is harmless because:
First, if a device tree specifies a "pcs-handle" but no select_pcs()
callback is provided by the platform driver, the created XPCS is never
used.
Is that accurate? The core consumes priv->hw->xpcs in two places that
do not go through plat->select_pcs():
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_phy_setup() {
/* If we have an xpcs, it defines which PHY interfaces are supported. */
if (priv->hw->xpcs)
pcs = xpcs_to_phylink_pcs(priv->hw->xpcs);
else
pcs = priv->hw->phylink_pcs;
if (pcs)
phy_interface_or(config->supported_interfaces, ...);
}
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_init_phy() {
if (priv->hw->xpcs &&
xpcs_get_an_mode(priv->hw->xpcs, mode) == DW_AN_C73)
return 0;
}
Without the XPCS, supported_interfaces stays limited to
plat->phy_interface and the DW_AN_C73 "no PHY" path is lost, so
stmmac_init_phy() can now fail where it previously succeeded.
Related, for Intel mGbE the callbacks are only installed inside the
SGMII/1000BASEX branch of intel_mgbe_common_data(), while the removed
common code created an XPCS from "pcs-handle" regardless of interface
mode. Should a warning be emitted when "pcs-handle" is present but no
pcs_init() claims it, or should the changelog state the behaviour change
explicitly?
>
> + ret = priv->plat->pcs_init(priv);
> if (ret)
> return dev_err_probe(priv->device, ret, "No xPCS found\n");
[Severity: Low]
This is a pre-existing wording problem and not introduced by this patch,
but with the generic XPCS code gone this is now the only diagnostic in
stmmac_pcs_setup(), and three of the four pcs_init() implementers do not
create an XPCS:
socfpga_dwmac_pcs_init() -> lynx_pcs_create_mdiodev()
rzn1_dwmac_pcs_init() -> miic_create()
renesas_gmac_pcs_init() -> miic_create()
So a miic_create() or lynx_pcs_create_mdiodev() failure, including
-EPROBE_DEFER, is reported as "No xPCS found". Would something like
"failed to initialise PCS" be more accurate here?
>
> - if (xpcs)
> - xpcs_config_eee_mult_fact(xpcs, priv->plat->mult_fact_100ns);
> -
> - priv->hw->xpcs = xpcs;
> -
> return 0;
> }
>
[ ... ]
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next v4 02/10] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
2026-09-07 15:37 ` [PATCH net-next v4 01/10] net: stmmac: move XPCS lifetime management to platform drivers Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-07 15:37 ` [PATCH net-next v4 03/10] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568 Coia Prant
` (8 subsequent siblings)
10 siblings, 0 replies; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
On RK3568, the SGMII interface can be routed to either GMAC0 or
GMAC1 via the pipe_sgmii_mac_sel bit in the pipe GRF registers.
Add the optional "rockchip,sgmii-mac-sel" property to allow the
device tree to select which GMAC controller is used for SGMII.
The property takes a value of 0 (GMAC0) or 1 (GMAC1). The hardware
reset value is 1 (GMAC1), but this can be overridden by setting the
property to 0 for boards where SGMII is connected to GMAC0.
This is necessary for boards such as the Ariaboard Photonicat, where
the SGMII interface is connected to GMAC0 and needs to be explicitly
configured.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../bindings/phy/phy-rockchip-naneng-combphy.yaml | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml b/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml
index 379b08bd9e97a..8e898bce9af73 100644
--- a/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml
+++ b/Documentation/devicetree/bindings/phy/phy-rockchip-naneng-combphy.yaml
@@ -80,6 +80,15 @@ properties:
description:
Some additional pipe settings are accessed through GRF regs.
+ rockchip,sgmii-mac-sel:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [0, 1]
+ default: 1
+ description:
+ Select gmac0 or gmac1 to be used as SGMII controller.
+ The hardware reset value is GMAC1 (1). Set this to 0 to route
+ SGMII to GMAC0.
+
"#phy-cells":
const: 1
@@ -105,6 +114,10 @@ allOf:
maxItems: 1
reset-names:
maxItems: 1
+ rockchip,sgmii-mac-sel: true
+ else:
+ properties:
+ rockchip,sgmii-mac-sel: false
- if:
properties:
compatible:
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* [PATCH net-next v4 03/10] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
2026-09-07 15:37 ` [PATCH net-next v4 01/10] net: stmmac: move XPCS lifetime management to platform drivers Coia Prant
2026-09-07 15:37 ` [PATCH net-next v4 02/10] dt-bindings: phy: rockchip: naneng-combphy: add rockchip,sgmii-mac-sel property Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:13 ` Jakub Kicinski
2026-09-07 15:37 ` [PATCH net-next v4 04/10] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
` (7 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
On RK3568, the SGMII interface can be routed to either GMAC0 or
GMAC1 via the GRF register pipe_sgmii_mac_sel.
Add support for this selection by introducing
the "rockchip,sgmii-mac-sel" DT property.
The hardware reset value is GMAC1 (1). If the property is set to 0,
the driver routes SGMII to GMAC0; if set to 1 (or omitted), it
remains at GMAC1.
This is necessary for boards such as the Ariaboard Photonicat, which
uses the SGMII interface connected to GMAC0.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 229)
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
drivers/phy/rockchip/phy-rockchip-naneng-combphy.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
index 7843356a4dd47..919bb97a4b182 100644
--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -186,6 +186,7 @@ struct rockchip_combphy_grfcfg {
struct combphy_reg pipe_xpcs_phy_ready;
struct combphy_reg pipe_pcie1l0_sel;
struct combphy_reg pipe_pcie1l1_sel;
+ struct combphy_reg pipe_sgmii_mac_sel;
struct combphy_reg u3otg0_port_en;
struct combphy_reg u3otg1_port_en;
};
@@ -212,6 +213,7 @@ struct rockchip_combphy_priv {
bool enable_ssc;
bool ext_refclk;
struct clk *refclk;
+ u32 sgmii_mac_sel;
};
static void rockchip_combphy_updatel(struct rockchip_combphy_priv *priv,
@@ -375,6 +377,9 @@ static int rockchip_combphy_parse_dt(struct device *dev, struct rockchip_combphy
priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
+ priv->sgmii_mac_sel = 1;
+ device_property_read_u32(dev, "rockchip,sgmii-mac-sel", &priv->sgmii_mac_sel);
+
priv->phy_rst = devm_reset_control_get_exclusive(dev, "phy");
/* fallback to old behaviour */
if (PTR_ERR(priv->phy_rst) == -ENOENT)
@@ -873,6 +878,8 @@ static int rk3568_combphy_cfg(struct rockchip_combphy_priv *priv)
break;
case PHY_TYPE_SGMII:
+ rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_sgmii_mac_sel,
+ priv->sgmii_mac_sel > 0);
rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_xpcs_phy_ready, true);
rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_phymode_sel, true);
rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_sel_qsgmii, true);
@@ -984,6 +991,7 @@ static const struct rockchip_combphy_grfcfg rk3568_combphy_grfcfgs = {
.con3_for_sata = { 0x000c, 15, 0, 0x00, 0x4407 },
/* pipe-grf */
.pipe_con0_for_sata = { 0x0000, 15, 0, 0x00, 0x2220 },
+ .pipe_sgmii_mac_sel = { 0x0040, 1, 1, 0x00, 0x01 },
.pipe_xpcs_phy_ready = { 0x0040, 2, 2, 0x00, 0x01 },
.u3otg0_port_en = { 0x0104, 15, 0, 0x0181, 0x1100 },
.u3otg1_port_en = { 0x0144, 15, 0, 0x0181, 0x1100 },
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 03/10] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
2026-09-07 15:37 ` [PATCH net-next v4 03/10] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568 Coia Prant
@ 2026-09-12 20:13 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:13 UTC (permalink / raw)
To: coiaprant
Cc: Jakub Kicinski, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, vkoul, maxime.chevallier,
mcoquelin.stm32, alexandre.torgue, prabhakar.mahadev-lad.rj,
romain.gantois, hkallweit1, neil.armstrong, linux, shawn.lin,
david, netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568
This adds a "rockchip,sgmii-mac-sel" DT property to the RK3568 combphy
driver so the SGMII interface can be routed to GMAC0 or GMAC1 through the
pipe GRF pipe_sgmii_mac_sel bit. The value defaults to 1 (GMAC1) when the
property is absent, and is written during PHY_TYPE_SGMII configuration.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 229)
A few questions below.
> diff --git a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
> index 7843356a4dd47..919bb97a4b182 100644
> --- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
> +++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
[ ... ]
> @@ -212,6 +213,7 @@ struct rockchip_combphy_priv {
> bool enable_ssc;
> bool ext_refclk;
> struct clk *refclk;
> + u32 sgmii_mac_sel;
> };
>
> static void rockchip_combphy_updatel(struct rockchip_combphy_priv *priv,
> @@ -375,6 +377,9 @@ static int rockchip_combphy_parse_dt(struct device *dev, struct rockchip_combphy
>
> priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
>
> + priv->sgmii_mac_sel = 1;
> + device_property_read_u32(dev, "rockchip,sgmii-mac-sel", &priv->sgmii_mac_sel);
> +
[Severity: Medium]
Since the return value of device_property_read_u32() is discarded here,
rockchip_combphy_parse_dt() cannot distinguish "the DT asked for GMAC1"
from "the DT said nothing about the mux".
The result is that rk3568_combphy_cfg() writes PIPE_GRF 0x0040 bit 1 on
every PHY_TYPE_SGMII bring-up, including for device trees that do not have
the new property. Before this patch that bit was never touched, so whatever
routing the SoC reset or the boot firmware established was left alone.
The commit message states:
"The hardware reset value is GMAC1 (1). If the property is set to 0,
the driver routes SGMII to GMAC0; if set to 1 (or omitted), it
remains at GMAC1."
The "or omitted" case now depends entirely on that reset value being 1. If
the reset value is actually 0, or if firmware routed SGMII to GMAC0 on a
board wired that way and its DT lacks the property, does phy_init() still
return success while the XPCS ends up attached to the wrong MAC, giving a
link that never comes up and no message explaining why?
Would it make sense to only write the bit when the property is actually
present, for example:
if (!device_property_read_u32(dev, "rockchip,sgmii-mac-sel",
&priv->sgmii_mac_sel))
priv->sgmii_mac_sel_valid = true;
so that omitting the property leaves the mux untouched?
> priv->phy_rst = devm_reset_control_get_exclusive(dev, "phy");
> /* fallback to old behaviour */
> if (PTR_ERR(priv->phy_rst) == -ENOENT)
> @@ -873,6 +878,8 @@ static int rk3568_combphy_cfg(struct rockchip_combphy_priv *priv)
> break;
>
> case PHY_TYPE_SGMII:
> + rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_sgmii_mac_sel,
> + priv->sgmii_mac_sel > 0);
[Severity: Low]
The binding added in the preceding patch of this series declares the
property as:
rockchip,sgmii-mac-sel:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [0, 1]
default: 1
but "priv->sgmii_mac_sel > 0" collapses the value to a boolean, and
rockchip_combphy_parse_dt() does no range check.
Does that mean a device tree setting the property to 2 or 0xffffffff is
silently treated as GMAC1 rather than rejected? Similarly, a
present-but-malformed property (wrong length or type) makes
device_property_read_u32() fail, and the pre-seeded default of 1 is used
with no diagnostic.
dtschema only validates in-tree device trees, so for bootloader-supplied
DTs or overlays the driver is the only place the enum can be enforced.
Would checking the value against {0, 1} and returning an error, or
alternatively documenting the property as a plain flag, close that gap?
[Severity: Low]
The mux write is only added to the PHY_TYPE_SGMII case. The adjacent
PHY_TYPE_QSGMII case in rk3568_combphy_cfg() programs the same pipe-grf
register (pipe_xpcs_phy_ready at offset 0x0040 bit 2) but never touches
the MAC-select bit:
case PHY_TYPE_QSGMII:
rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_xpcs_phy_ready, true);
...
The binding gates the property on the compatible only, not on the PHY
type, and rockchip_combphy_parse_dt() runs at probe time before the PHY
type is known from rockchip_combphy_xlate().
If a device tree sets rockchip,sgmii-mac-sel on a combphy that a consumer
later claims with PHY_TYPE_QSGMII, is the requested routing simply dropped
with no warning? If the property is meant to be SGMII-only, would
rejecting or warning about it for other PHY types make that explicit?
> rockchip_combphy_param_write(priv->pipe_grf, &cfg->pipe_xpcs_phy_ready, true);
> rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_phymode_sel, true);
> rockchip_combphy_param_write(priv->phy_grf, &cfg->pipe_sel_qsgmii, true);
> @@ -984,6 +991,7 @@ static const struct rockchip_combphy_grfcfg rk3568_combphy_grfcfgs = {
> .con3_for_sata = { 0x000c, 15, 0, 0x00, 0x4407 },
> /* pipe-grf */
> .pipe_con0_for_sata = { 0x0000, 15, 0, 0x00, 0x2220 },
> + .pipe_sgmii_mac_sel = { 0x0040, 1, 1, 0x00, 0x01 },
> .pipe_xpcs_phy_ready = { 0x0040, 2, 2, 0x00, 0x01 },
> .u3otg0_port_en = { 0x0104, 15, 0, 0x0181, 0x1100 },
> .u3otg1_port_en = { 0x0144, 15, 0, 0x0181, 0x1100 },
For reference on the first two comments: this disable/enable encoding
({ 0x0040, 1, 1, 0x00, 0x01 }) is what maps the DT value onto GMAC0/GMAC1,
and rockchip_combphy_param_write() always sets the write-enable mask:
tmp = en ? reg->enable : reg->disable;
mask = GENMASK(reg->bitend, reg->bitstart);
val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT);
so the bit is committed to hardware on every SGMII bring-up regardless of
whether the device tree expressed a preference. Could the TRM bit polarity
and the claimed reset value of 1 be confirmed in the commit message?
Note that no in-tree Rockchip device tree requests PHY_TYPE_SGMII before
this series, and the only user (rk3568-photonicat.dts) is added later in
the series, so nothing in-tree changes behaviour today.
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next v4 04/10] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (2 preceding siblings ...)
2026-09-07 15:37 ` [PATCH net-next v4 03/10] phy: rockchip: naneng-combphy: add SGMII MAC selection for RK3568 Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:13 ` Jakub Kicinski
2026-09-07 15:37 ` [PATCH net-next v4 05/10] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes Coia Prant
` (6 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
Add device tree binding documentation for the Synopsys DesignWare
XPCS integrated on the Rockchip RK3568 SoC.
The XPCS is accessed over the APB3 bus and internally connected to
a Naneng Combo SerDes PHY. It supports 1000BASE-X, SGMII, and
QSGMII modes, with four MII ports.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../bindings/net/pcs/rockchip-dwxpcs.yaml | 110 ++++++++++++++++++
1 file changed, 110 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml
diff --git a/Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml b/Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml
new file mode 100644
index 0000000000000..0852d0bcb66a2
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml
@@ -0,0 +1,110 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/pcs/rockchip-dwxpcs.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Rockchip RK3568 Synopsys DesignWare Ethernet PCS
+
+maintainers:
+ - Coia Prant <coiaprant@gmail.com>
+
+description: |
+ Rockchip RK3568 SoC integrates a Synopsys DesignWare Ethernet Physical
+ Coding Sublayer (XPCS).
+ The PCS provides an interface between the Media Access Control (MAC)
+ and the Physical Medium Attachment (PMA) sublayer through a Media
+ Independent Interface (GMII).
+
+ The XPCS is accessed over the APB3 bus and internally connected to a
+ Naneng Combo SerDes PHY.
+ It supports 1000BASE-X, SGMII and QSGMII modes.
+
+ The block contains four MII ports that can be individually enabled and
+ routed to one of the Ethernet GMAC controllers via the pcs-handle
+ property in the MAC device tree node.
+
+properties:
+ compatible:
+ const: rockchip,rk3568-xpcs
+
+ reg:
+ maxItems: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ clocks:
+ items:
+ - description: APB3 bus interface clock (clk_csr_i), required for register access
+ - description: EEE clock (clk_eee_i), required for Energy Efficient Ethernet operation
+
+ clock-names:
+ items:
+ - const: csr
+ - const: eee
+
+ phys:
+ maxItems: 1
+
+ phy-names:
+ const: serdes
+
+ power-domains:
+ maxItems: 1
+
+patternProperties:
+ "^pcs-mii@[0-3]$":
+ type: object
+ description:
+ One of the four MII ports of the XPCS. The port is linked to an
+ Ethernet MAC controller via the pcs-handle property in the MAC's
+ device tree node.
+
+ properties:
+ reg:
+ description: MII port number.
+ enum: [0, 1, 2, 3]
+
+ required:
+ - reg
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+ - clocks
+ - clock-names
+ - phys
+ - phy-names
+ - power-domains
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/rk3568-cru.h>
+ #include <dt-bindings/power/rk3568-power.h>
+ #include <dt-bindings/phy/phy.h>
+
+ pcs@fda00000 {
+ compatible = "rockchip,rk3568-xpcs";
+ reg = <0xfda00000 0x200000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
+ clock-names = "csr", "eee";
+ phys = <&combphy2 PHY_TYPE_SGMII>;
+ phy-names = "serdes";
+ power-domains = <&power RK3568_PD_PIPE>;
+
+ pcs-mii@0 {
+ reg = <0>;
+ };
+ };
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 04/10] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
2026-09-07 15:37 ` [PATCH net-next v4 04/10] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
@ 2026-09-12 20:13 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:13 UTC (permalink / raw)
To: coiaprant
Cc: Jakub Kicinski, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, vkoul, maxime.chevallier,
mcoquelin.stm32, alexandre.torgue, prabhakar.mahadev-lad.rj,
romain.gantois, hkallweit1, neil.armstrong, linux, shawn.lin,
david, netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
dt-bindings: net: pcs: add rockchip,rk3568-xpcs support
This adds a new standalone schema describing the Synopsys DesignWare XPCS
as integrated on the Rockchip RK3568, with a CSR window, csr/eee clocks, a
Naneng Combo SerDes phy, a power domain and four MII child ports. A few
questions about the property set and the file naming below.
> diff --git a/Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml b/Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml
> new file mode 100644
> index 0000000000000..0852d0bcb66a2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml
> @@ -0,0 +1,110 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/pcs/rockchip-dwxpcs.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
[Severity: Low]
This isn't a bug, but should the file be named after the documented
compatible, i.e. rockchip,rk3568-xpcs.yaml? The other schemas in
Documentation/devicetree/bindings/net/pcs/ all use that form:
snps,dw-xpcs.yaml, fsl,lynx-pcs.yaml, mediatek,sgmiisys.yaml,
renesas,rzn1-miic.yaml.
A rename would also have to update the $id line above, since it encodes the
same name.
Related: the example at the bottom uses pcs@fda00000 and pcs-mii@0, while
the sibling PCS binding uses the generic ethernet-pcs@... node name, and no
$nodename constraint is added here. The SoC dtsi added later in the series
copies the pcs@/pcs-mii@ names verbatim.
> +
> +title: Rockchip RK3568 Synopsys DesignWare Ethernet PCS
[ ... ]
> +properties:
> + compatible:
> + const: rockchip,rk3568-xpcs
[Severity: Low]
This isn't a bug, but would a set of SoC-specific if/then blocks in
Documentation/devicetree/bindings/net/pcs/snps,dw-xpcs.yaml be preferred
over a second schema for the same IP?
To be clear, a snps,dw-xpcs fallback compatible or a $ref to that schema
would be wrong here. The glue driver added later in the series builds CSR
addresses with only a 3-bit MMD field:
return FIELD_PREP(0x70000, dev) | FIELD_PREP(0xffff, reg);
and remaps the vendor MMDs in xpcs_rk_mdio_read_remapping() /
xpcs_rk_mdio_write_remapping(), so the generic direct mapping described in
snps,dw-xpcs.yaml would compute different offsets.
So the only question is where the description should live, not whether the
compatible should be shared.
> +
> + reg:
> + maxItems: 1
[Severity: Low]
Should the sbd_intr_o output be described as an optional interrupts
property? With additionalProperties: false at the end of the file, a
conforming device tree cannot carry it, although adding it later stays
backward compatible.
The single unnamed 2 MiB window itself does match the driver:
xpcs_rk_init_res() uses platform_get_resource(pdev, IORESOURCE_MEM, 0) and
requires resource_size(res) >= SZ_2M, and the largest computed offset
0x7FFFF << 2 = 0x1FFFFC still falls inside 0x200000, so reg-names and
reg-io-width are not needed.
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 0
> +
> + clocks:
> + items:
> + - description: APB3 bus interface clock (clk_csr_i), required for register access
> + - description: EEE clock (clk_eee_i), required for Energy Efficient Ethernet operation
> +
> + clock-names:
> + items:
> + - const: csr
> + - const: eee
> +
> + phys:
> + maxItems: 1
> +
> + phy-names:
> + const: serdes
> +
> + power-domains:
> + maxItems: 1
[Severity: Low]
Should resets/reset-names be described here too? The RK3568 CRU exposes
four dedicated reset lines for this block 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
Since the schema ends with additionalProperties: false, a conforming device
tree cannot express them at all, and the glue driver added later in the
series never requests a reset control, so it depends on those resets already
being de-asserted.
Peer blocks in the same island do describe theirs, e.g. combphy0 with
resets = <&cru SRST_PIPEPHY0> and gmac0 with resets = <&cru SRST_A_GMAC0>.
Note the XPCS core does provide a register-level reset via xpcs_soft_reset()
in drivers/net/pcs/pcs-xpcs.c, so a state-machine restart is possible
without the CRU resets, and an optional resets property can be added later
without breaking the ABI.
> +
> +patternProperties:
> + "^pcs-mii@[0-3]$":
> + type: object
> + description:
> + One of the four MII ports of the XPCS. The port is linked to an
> + Ethernet MAC controller via the pcs-handle property in the MAC's
> + device tree node.
> +
> + properties:
> + reg:
> + description: MII port number.
> + enum: [0, 1, 2, 3]
[Severity: Low]
This isn't a bug, but is pcs-mii@N the intended name for these per-port
children rather than the generic ethernet-pcs@N?
Resolving a MAC pcs-handle that points at one of these children does work
with the rest of the series: xpcs_rk_create() in
drivers/net/pcs/pcs-xpcs-rk.c reads the child reg, walks up with
of_get_parent() and calls xpcs_create_mdiodev(pxpcs->bus, port), and
rk_pcs_init() in drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c does
of_parse_phandle(np, "pcs-handle", 0). So the open point is just the node
naming.
> +
> + required:
> + - reg
> +
> + additionalProperties: false
> +
> +required:
> + - compatible
> + - reg
> + - "#address-cells"
> + - "#size-cells"
> + - clocks
> + - clock-names
> + - phys
> + - phy-names
> + - power-domains
[Severity: Medium]
Does requiring phys and phy-names here make dtbs_check fail for the RK3568
boards? The SoC node added later in the same series omits both, because the
SerDes selection is board specific:
arch/arm64/boot/dts/rockchip/rk3568.dtsi:
xpcs: pcs@fda00000 {
compatible = "rockchip,rk3568-xpcs";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0xfda00000 0x0 0x200000>;
clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
clock-names = "csr", "eee";
power-domains = <&power RK3568_PD_PIPE>;
status = "disabled";
and only the board fills them in:
arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts:
&xpcs {
status = "okay";
phys = <&combphy2 PHY_TYPE_SGMII>;
phy-names = "serdes";
};
dtschema validates nodes irrespective of status = "disabled", so wouldn't
every rk3568*.dtb apart from the Photonicat report "'phys', 'phy-names' are
required properties"?
Should phys/phy-names be dropped from required (or made conditional), or
should the SoC dtsi carry them instead?
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/clock/rk3568-cru.h>
> + #include <dt-bindings/power/rk3568-power.h>
> + #include <dt-bindings/phy/phy.h>
> +
> + pcs@fda00000 {
> + compatible = "rockchip,rk3568-xpcs";
> + reg = <0xfda00000 0x200000>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
> + clock-names = "csr", "eee";
> + phys = <&combphy2 PHY_TYPE_SGMII>;
> + phy-names = "serdes";
> + power-domains = <&power RK3568_PD_PIPE>;
> +
> + pcs-mii@0 {
> + reg = <0>;
> + };
> + };
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next v4 05/10] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (3 preceding siblings ...)
2026-09-07 15:37 ` [PATCH net-next v4 04/10] dt-bindings: net: pcs: add rockchip,rk3568-xpcs support Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:13 ` Jakub Kicinski
2026-09-07 15:37 ` [PATCH net-next v4 06/10] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
` (5 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The RK3568 SoC integrates a Synopsys DesignWare XPCS that provides
the Physical Coding Sublayer for 1000BASE-X, SGMII, and QSGMII
interfaces via its four MII ports. Add the XPCS device node and
its pcs-mii sub-nodes to the SoC device tree.
The XPCS device is accessed via the APB3 bus at 0xfda00000 and
requires the CSR clock (PCLK_XPCS) for register access and the EEE
clock (CLK_XPCS_EEE) for Energy Efficient Ethernet operation. The
PD_PIPE power domain must be enabled before any register access.
Also add two fixed-clock nodes (xpcs_gmac0_clk and xpcs_gmac1_clk)
providing the 125 MHz reference clock for the GMACs when operating
with XPCS. These clocks are used as the assigned-clock-parents
for the respective GMAC nodes in board-level device trees.
The XPCS node and its pcs-mii sub-nodes are disabled by default and
must be enabled at the board level when 1000BASE-X/SGMII/QSGMII is
in use. The fixed-clock nodes are always present and do not have a
status property, as they are static clock sources.
The XPCS node also requires a reference to the appropriate Naneng
Combo PHY via the phys property at the board level.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
arch/arm64/boot/dts/rockchip/rk3568.dtsi | 45 ++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/arch/arm64/boot/dts/rockchip/rk3568.dtsi b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
index 3bc653f027f1f..989e164c0eb39 100644
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
@@ -110,6 +110,51 @@ sata0: sata@fc000000 {
status = "disabled";
};
+ xpcs: pcs@fda00000 {
+ compatible = "rockchip,rk3568-xpcs";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x0 0xfda00000 0x0 0x200000>;
+ clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
+ clock-names = "csr", "eee";
+ power-domains = <&power RK3568_PD_PIPE>;
+ status = "disabled";
+
+ xpcs_mii0: pcs-mii@0 {
+ reg = <0>;
+ status = "disabled";
+ };
+
+ xpcs_mii1: pcs-mii@1 {
+ reg = <1>;
+ status = "disabled";
+ };
+
+ xpcs_mii2: pcs-mii@2 {
+ reg = <2>;
+ status = "disabled";
+ };
+
+ xpcs_mii3: pcs-mii@3 {
+ reg = <3>;
+ status = "disabled";
+ };
+ };
+
+ xpcs_gmac0_clk: xpcs-gmac0-clock {
+ compatible = "fixed-clock";
+ clock-frequency = <125000000>;
+ clock-output-names = "clk_gmac0_xpcs_mii";
+ #clock-cells = <0>;
+ };
+
+ xpcs_gmac1_clk: xpcs-gmac1-clock {
+ compatible = "fixed-clock";
+ clock-frequency = <125000000>;
+ clock-output-names = "clk_gmac1_xpcs_mii";
+ #clock-cells = <0>;
+ };
+
pipe_phy_grf0: syscon@fdc70000 {
compatible = "rockchip,rk3568-pipe-phy-grf", "syscon";
reg = <0x0 0xfdc70000 0x0 0x1000>;
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 05/10] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
2026-09-07 15:37 ` [PATCH net-next v4 05/10] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes Coia Prant
@ 2026-09-12 20:13 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:13 UTC (permalink / raw)
To: coiaprant
Cc: Jakub Kicinski, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, vkoul, maxime.chevallier,
mcoquelin.stm32, alexandre.torgue, prabhakar.mahadev-lad.rj,
romain.gantois, hkallweit1, neil.armstrong, linux, shawn.lin,
david, netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes
This adds the RK3568 Synopsys DesignWare XPCS node (with four pcs-mii
sub-nodes) plus two 125 MHz fixed-clock nodes used as GMAC reference
clocks. The XPCS node and its sub-nodes are disabled by default and are
meant to be completed and enabled at the board level.
A couple of questions about the new nodes below.
> diff --git a/arch/arm64/boot/dts/rockchip/rk3568.dtsi b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
> index 3bc653f027f1f..989e164c0eb39 100644
> --- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
> +++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
> @@ -110,6 +110,51 @@ sata0: sata@fc000000 {
> status = "disabled";
> };
>
> + xpcs: pcs@fda00000 {
> + compatible = "rockchip,rk3568-xpcs";
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = <0x0 0xfda00000 0x0 0x200000>;
> + clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
> + clock-names = "csr", "eee";
> + power-domains = <&power RK3568_PD_PIPE>;
> + status = "disabled";
[Severity: Medium]
Does this node validate against the binding added earlier in the same
series? Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml
lists phys and phy-names unconditionally:
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- clocks
- clock-names
- phys
- phy-names
- power-domains
The node here supplies neither, and dt-validate does not skip nodes with
status = "disabled", so dtbs_check should report "'phys' is a required
property" and "'phy-names' is a required property" for every rk3568 dtb
that includes this dtsi.
At the tip of the series only rk3568-photonicat.dts fills them in:
&xpcs {
status = "okay";
phys = <&combphy2 PHY_TYPE_SGMII>;
phy-names = "serdes";
};
so rk3568-evb, rk3568-rock-3a, rk3568-roc-pc, rk3568-radxa-cm3j,
rk3568-wolfvision-pf5 and friends would still be missing them.
If which combphy carries SGMII is genuinely a board-level choice, should
phys/phy-names be moved out of the unconditional required: list in the
binding instead, or should the dtsi provide a default?
> +
> + xpcs_mii0: pcs-mii@0 {
> + reg = <0>;
> + status = "disabled";
> + };
[Severity: Low]
This isn't a bug, just a note for the record: at this commit there is no
in-tree consumer of "rockchip,rk3568-xpcs" or of the pcs-mii sub-nodes.
Both arrive later in the same series - xpcs_rk_of_ids in
drivers/net/pcs/pcs-xpcs-rk.c gains the compatible, and rk_pcs_init() in
drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c parses pcs-handle and
calls xpcs_rk_create(). With the nodes disabled by default the
intermediate state looks harmless, so nothing needs changing here.
> +
> + xpcs_mii1: pcs-mii@1 {
> + reg = <1>;
> + status = "disabled";
> + };
> +
> + xpcs_mii2: pcs-mii@2 {
> + reg = <2>;
> + status = "disabled";
> + };
> +
> + xpcs_mii3: pcs-mii@3 {
> + reg = <3>;
> + status = "disabled";
> + };
> + };
> +
> + xpcs_gmac0_clk: xpcs-gmac0-clock {
> + compatible = "fixed-clock";
> + clock-frequency = <125000000>;
> + clock-output-names = "clk_gmac0_xpcs_mii";
> + #clock-cells = <0>;
> + };
> +
> + xpcs_gmac1_clk: xpcs-gmac1-clock {
> + compatible = "fixed-clock";
> + clock-frequency = <125000000>;
> + clock-output-names = "clk_gmac1_xpcs_mii";
> + #clock-cells = <0>;
> + };
[Severity: Low]
These two node names take the deprecated branch of the fixed-clock
$nodename schema. Documentation/devicetree/bindings/clock/fixed-clock.yaml
has:
$nodename:
anyOf:
- description:
Preferred name is 'clock-<freq>' with <freq> being the output
frequency as defined in the 'clock-frequency' property.
pattern: "^clock-([0-9]+|[a-z0-9-]+)$"
- description: Any name allowed
deprecated: true
xpcs-gmac0-clock and xpcs-gmac1-clock do not match the preferred
pattern, so they land on the "Any name allowed" branch. Newer Rockchip
dtsi files use the preferred form already, for example rk3528.dtsi with
xin24m: clock-xin24m and gmac0_clk: clock-gmac50m.
Could these be named clock-xpcs-gmac0 and clock-xpcs-gmac1? The labels
xpcs_gmac0_clk / xpcs_gmac1_clk and all references would stay the same.
> pipe_phy_grf0: syscon@fdc70000 {
> compatible = "rockchip,rk3568-pipe-phy-grf", "syscon";
> reg = <0x0 0xfdc70000 0x0 0x1000>;
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next v4 06/10] net: pcs: xpcs: add ANRESTART support for SGMII link recovery
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (4 preceding siblings ...)
2026-09-07 15:37 ` [PATCH net-next v4 05/10] arm64: dts: rockchip: rk3568: add XPCS and fixed-clock nodes Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:13 ` Jakub Kicinski
2026-09-07 15:37 ` [PATCH net-next v4 07/10] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
` (4 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc,
Jiawen Wu
On some hardware using the DesignWare XPCS IP (e.g., RK3568 MAC side
SGMII), the PCS does not automatically restart auto-negotiation when the
link goes down and comes back up. Without an explicit ANRESTART, the link
stays down forever.
Add BMCR_ANRESTART in two places:
1. In xpcs_config_aneg_c37_sgmii(), when starting AN, set ANRESTART
alongside ANENABLE to initiate a fresh negotiation.
2. In xpcs_get_state_c37_sgmii(), when link is down and AN completion is
detected, clear the interrupt and trigger ANRESTART to restart the
negotiation process.
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 | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index 0337e2bcc0125..50b9cdf94fdaa 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -816,9 +816,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,10 +1098,15 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs,
return 0;
}
- /* Clear AN complete status or interrupt */
- if (state->an_complete)
+ if (state->an_complete) {
+ /* Clear AN complete status or interrupt */
xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);
+ /* Initiate the next round of AN */
+ xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,
+ BMCR_ANRESTART);
+ }
+
return 0;
}
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 06/10] net: pcs: xpcs: add ANRESTART support for SGMII link recovery
2026-09-07 15:37 ` [PATCH net-next v4 06/10] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
@ 2026-09-12 20:13 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:13 UTC (permalink / raw)
To: coiaprant
Cc: Jakub Kicinski, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, vkoul, maxime.chevallier,
mcoquelin.stm32, alexandre.torgue, prabhakar.mahadev-lad.rj,
romain.gantois, hkallweit1, neil.armstrong, linux, shawn.lin,
david, netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: pcs: xpcs: add ANRESTART support for SGMII link recovery
The patch sets BMCR_ANRESTART together with BMCR_ANENABLE when configuring
C37 SGMII in-band negotiation, and clears the latched CL37 AN complete
status before doing so. It also restarts negotiation from
xpcs_get_state_c37_sgmii() when AN completion is latched but the link is
reported down.
> 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.
[Severity: Low]
The changelog describes a user visible failure ("the link stays down
forever") in long standing shared code, but there is no Fixes: tag and no
statement about which histories are affected.
The only hardware named here, RK3568 XPCS, does not exist in the baseline
tree: searching for xpcs_rk, rk3568-xpcs or pcs-xpcs-rk under drivers/net at
879e280b8486 returns nothing, and the glue driver arrives later in this
series ("net: pcs: xpcs: add Rockchip RK3568 platform glue driver").
Could the changelog either name the pre-existing commit being fixed, or say
explicitly that the affected platform is introduced later in the same
series, so that stable tooling does not have to guess?
> diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
> index 0337e2bcc0125..50b9cdf94fdaa 100644
> --- a/drivers/net/pcs/pcs-xpcs.c
> +++ b/drivers/net/pcs/pcs-xpcs.c
> @@ -816,9 +816,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]
Should the comment at the top of xpcs_config_aneg_c37_sgmii() be updated
along with this change? It still says the opposite of what the code now
does:
drivers/net/pcs/pcs-xpcs.c:xpcs_config_aneg_c37_sgmii() {
/* For AN for C37 SGMII mode, the settings are :-
...
* 5) VR_MII_MMD_CTRL Bit(12) [AN_ENABLE] = 1b (Enable SGMII AN)
*
* Note that VR_MII_MMD_CTRL is MII_BMCR.
*
* Note: Since it is MAC side SGMII, there is no need to set
* SR_MII_AN_ADV. ... There is also no need to
* trigger AN restart for MAC-side SGMII.
*/
On the default path this same function selects MAC side SGMII:
} else {
tx_conf = DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII;
}
so the restart is now issued for exactly the case the comment says does not
need it. The new DW_VR_MII_AN_INTR_STS write is also a sixth step that the
enumerated sequence does not mention.
If the databook statement no longer holds for these parts, could the
comment be corrected, so that a later reader does not remove the ANRESTART
bit as a mistake?
> @@ -1093,10 +1098,15 @@ static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs,
> return 0;
> }
>
> - /* Clear AN complete status or interrupt */
> - if (state->an_complete)
> + if (state->an_complete) {
> + /* Clear AN complete status or interrupt */
> xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_AN_INTR_STS, 0);
>
> + /* Initiate the next round of AN */
> + xpcs_modify(xpcs, MDIO_MMD_VEND2, MII_BMCR, BMCR_ANRESTART,
> + BMCR_ANRESTART);
> + }
> +
> return 0;
> }
[Severity: Medium]
Can this sequence get stuck if the xpcs_modify() fails?
The latched status is cleared first, and DW_VR_MII_AN_STS_C37_ANCMPLT_INTR
is the only thing that brings execution back into this branch:
state->an_complete = ret & DW_VR_MII_AN_STS_C37_ANCMPLT_INTR;
So if the clear succeeds but the restart does not, every later poll reads
ANCMPLT as 0, state->an_complete stays false, and the restart is never
retried, leaving the port down with AN idle until something calls
pcs_config again.
mdiodev_c45_modify() does a read followed by a write, so there are two
places it can return an error, for example a bus -ETIMEDOUT, or with the
Rockchip glue added later in this series the pm_runtime_resume_and_get()
error path in xpcs_rk_read_reg() / xpcs_rk_write_reg().
Both return values are also discarded and the function still ends with
return 0, so the diagnostic in the caller can never fire for them:
case DW_AN_C37_SGMII:
ret = xpcs_get_state_c37_sgmii(xpcs, state);
if (ret)
dev_err(&xpcs->mdiodev->dev, "%s returned %pe\n",
"xpcs_get_state_c37_sgmii", ERR_PTR(ret));
Would it be better to assert BMCR_ANRESTART before clearing the latched
status, and to propagate both return codes to xpcs_get_state()?
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next v4 07/10] net: pcs: xpcs: add Rockchip RK3568 platform glue driver
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (5 preceding siblings ...)
2026-09-07 15:37 ` [PATCH net-next v4 06/10] net: pcs: xpcs: add ANRESTART support for SGMII link recovery Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:13 ` Jakub Kicinski
2026-09-07 15:37 ` [PATCH net-next v4 08/10] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
` (3 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The RK3568 SoC integrates a Synopsys DesignWare XPCS that is accessed
via APB3 memory-mapped registers.
This driver provides the glue logic to make the XPCS accessible to
the generic pcs-xpcs core.
The XPCS block contains four MII ports (0..3), each of which can be
routed to GMAC0 or GMAC1 via the pcs-handle property in the MAC node.
The hardware maps these ports to different MMDs:
- port 0: MMD 7 (ROCKCHIP_MMD_MII)
- port 1: MMD 2 (ROCKCHIP_MMD_MII1)
- port 2: MMD 3 (ROCKCHIP_MMD_MII2)
- port 3: MMD 4 (ROCKCHIP_MMD_MII3)
This driver creates a virtual MDIO bus that translates MDIO operations
to APB3 register accesses, with proper address remapping for each port.
The generic xpcs driver then creates a phylink_pcs instance on top of
this bus, allowing the MAC to use the PCS via the standard phylink API.
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 | 23 ++
drivers/net/pcs/Makefile | 5 +-
drivers/net/pcs/pcs-xpcs-rk.c | 543 ++++++++++++++++++++++++++++++++
include/linux/pcs/pcs-xpcs-rk.h | 11 +
4 files changed, 580 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..88bc40791e69f 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -12,6 +12,29 @@ 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)
+ 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..68bcaa1a5cbbf
--- /dev/null
+++ b/drivers/net/pcs/pcs-xpcs-rk.c
@@ -0,0 +1,543 @@
+// 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/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 "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;
+};
+
+static ptrdiff_t xpcs_rk_addr_format(int dev, int reg)
+{
+ return FIELD_PREP(0x70000, dev) | FIELD_PREP(0xffff, reg);
+}
+
+static int xpcs_rk_read_reg(struct dw_xpcs_rk *pxpcs, int dev, int reg)
+{
+ ptrdiff_t csr;
+ int ret;
+
+ csr = xpcs_rk_addr_format(dev, reg);
+
+ ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
+ if (ret)
+ return ret;
+
+ ret = readl(pxpcs->reg_base + (csr << 2)) & 0xffff;
+
+ pm_runtime_put(&pxpcs->pdev->dev);
+ return ret;
+}
+
+static int xpcs_rk_write_reg(struct dw_xpcs_rk *pxpcs, int dev, int reg, u16 val)
+{
+ ptrdiff_t csr;
+ int ret;
+
+ csr = xpcs_rk_addr_format(dev, reg);
+
+ ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
+ if (ret)
+ return ret;
+
+ writel(val, pxpcs->reg_base + (csr << 2));
+
+ pm_runtime_put(&pxpcs->pdev->dev);
+ return 0;
+}
+
+#define ROCKCHIP_MMD_MII1 2
+#define ROCKCHIP_MMD_MII2 3
+#define ROCKCHIP_MMD_MII3 4
+#define ROCKCHIP_MMD_PMAPMD 6
+#define ROCKCHIP_MMD_MII 7
+
+static bool xpcs_rk_mdio_addr_validate(int addr)
+{
+ return !(addr < 0 || addr > 3);
+}
+
+static int xpcs_rk_mdio_read_remapping(int addr, int dev, int reg)
+{
+ switch (dev) {
+ case MDIO_MMD_PMAPMD:
+ return ROCKCHIP_MMD_PMAPMD;
+ case MDIO_MMD_VEND2:
+ break;
+ default:
+ return -ENXIO;
+ }
+
+ /* read remapping to MII is performed by HW */
+ 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;
+ }
+
+ /* Writable only on MII */
+ switch (reg) {
+ case DW_VR_MII_AN_CTRL:
+ case DW_VR_MII_AN_INTR_STS:
+ case DW_VR_MII_EEE_MCTRL0:
+ case DW_VR_MII_EEE_MCTRL1:
+ case DW_VR_MII_DIG_CTRL2:
+ return ROCKCHIP_MMD_MII;
+ default:
+ break;
+ }
+
+ switch (addr) {
+ case 0:
+ return ROCKCHIP_MMD_MII;
+ case 1:
+ return ROCKCHIP_MMD_MII1;
+ case 2:
+ return ROCKCHIP_MMD_MII2;
+ case 3:
+ return ROCKCHIP_MMD_MII3;
+ default:
+ return -ENODEV;
+ }
+}
+
+static int xpcs_rk_read_c22(struct mii_bus *bus, int addr, int reg)
+{
+ struct dw_xpcs_rk *pxpcs = bus->priv;
+ int dev;
+
+ if (!xpcs_rk_mdio_addr_validate(addr))
+ return -ENODEV;
+
+ dev = xpcs_rk_mdio_read_remapping(addr, MDIO_MMD_VEND2, reg);
+ if (dev < 0)
+ return 0xffff;
+
+ return xpcs_rk_read_reg(pxpcs, dev, reg);
+}
+
+static int xpcs_rk_write_c22(struct mii_bus *bus, int addr, int reg, u16 val)
+{
+ struct dw_xpcs_rk *pxpcs = bus->priv;
+ int dev;
+
+ if (!xpcs_rk_mdio_addr_validate(addr))
+ return -ENODEV;
+
+ dev = xpcs_rk_mdio_write_remapping(addr, MDIO_MMD_VEND2, reg);
+ if (dev < 0)
+ return 0;
+
+ return xpcs_rk_write_reg(pxpcs, dev, reg, val);
+}
+
+static int xpcs_rk_read_c45(struct mii_bus *bus, int addr, int dev, int reg)
+{
+ struct dw_xpcs_rk *pxpcs = bus->priv;
+
+ if (!xpcs_rk_mdio_addr_validate(addr))
+ return -ENODEV;
+
+ dev = xpcs_rk_mdio_read_remapping(addr, dev, reg);
+ if (dev < 0)
+ return 0xffff;
+
+ return xpcs_rk_read_reg(pxpcs, dev, reg);
+}
+
+static int xpcs_rk_write_c45(struct mii_bus *bus, int addr, int dev, int reg, u16 val)
+{
+ struct dw_xpcs_rk *pxpcs = bus->priv;
+
+ if (!xpcs_rk_mdio_addr_validate(addr))
+ return -ENODEV;
+
+ dev = xpcs_rk_mdio_write_remapping(addr, dev, reg);
+ if (dev < 0)
+ return 0;
+
+ return xpcs_rk_write_reg(pxpcs, dev, reg, val);
+}
+
+static struct dw_xpcs_rk *xpcs_rk_create_data(struct platform_device *pdev)
+{
+ struct dw_xpcs_rk *pxpcs;
+
+ pxpcs = devm_kzalloc(&pdev->dev, sizeof(*pxpcs), GFP_KERNEL);
+ if (!pxpcs)
+ return ERR_PTR(-ENOMEM);
+
+ pxpcs->pdev = pdev;
+
+ dev_set_drvdata(&pdev->dev, pxpcs);
+
+ return pxpcs;
+}
+
+static int xpcs_rk_serdes_phy_init(struct dw_xpcs_rk *pxpcs)
+{
+ struct device *dev = &pxpcs->pdev->dev;
+
+ pxpcs->serdes_phy = devm_phy_get(dev, "serdes");
+ if (IS_ERR(pxpcs->serdes_phy))
+ return dev_err_probe(dev, PTR_ERR(pxpcs->serdes_phy),
+ "Failed to get SerDes PHY\n");
+
+ return 0;
+}
+
+static void xpcs_rk_serdes_phy_poweroff(void *data)
+{
+ struct dw_xpcs_rk *pxpcs = data;
+ struct device *dev = &pxpcs->pdev->dev;
+
+ phy_power_off(pxpcs->serdes_phy);
+ phy_exit(pxpcs->serdes_phy);
+
+ dev_pm_genpd_rpm_always_on(dev, false);
+}
+
+static int xpcs_rk_serdes_phy_poweron(struct dw_xpcs_rk *pxpcs)
+{
+ struct device *dev = &pxpcs->pdev->dev;
+ int ret;
+
+ /*
+ * The power domain is required and must be enabled, which allows us to
+ * dynamically turn the CSR clock on/off using PM while keeping the PCS
+ * powered on.
+ */
+ ret = dev_pm_genpd_rpm_always_on(dev, true);
+ if (ret) {
+ dev_err(dev, "Failed to power on power-domains\n");
+ return ret;
+ }
+
+ ret = phy_init(pxpcs->serdes_phy);
+ if (ret) {
+ dev_err(dev, "Failed to init SerDes PHY\n");
+ goto pm_domain;
+ }
+
+ ret = phy_power_on(pxpcs->serdes_phy);
+ if (ret) {
+ dev_err(dev, "Failed to power on SerDes PHY\n");
+ goto serdes_phy;
+ }
+
+ ret = devm_add_action_or_reset(dev, xpcs_rk_serdes_phy_poweroff, pxpcs);
+ if (ret) {
+ dev_err(dev, "Failed to register devm for SerDes PHY: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+
+serdes_phy:
+ phy_exit(pxpcs->serdes_phy);
+pm_domain:
+ dev_pm_genpd_rpm_always_on(dev, false);
+ return ret;
+}
+
+static int xpcs_rk_init_res(struct dw_xpcs_rk *pxpcs)
+{
+ struct platform_device *pdev = pxpcs->pdev;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "No reg-space found\n");
+ return -EINVAL;
+ }
+
+ if (resource_size(res) < SZ_2M) {
+ dev_err(dev, "Invalid reg-space size\n");
+ return -EINVAL;
+ }
+
+ pxpcs->reg_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(pxpcs->reg_base)) {
+ dev_err(dev, "Failed to map reg-space\n");
+ return PTR_ERR(pxpcs->reg_base);
+ }
+
+ return 0;
+}
+
+static void xpcs_rk_exit_clk(void *data)
+{
+ struct dw_xpcs_rk *pxpcs = data;
+ struct device *dev = &pxpcs->pdev->dev;
+
+ clk_disable_unprepare(pxpcs->eee_clk);
+
+ pm_runtime_force_suspend(dev);
+}
+
+static int xpcs_rk_init_clk(struct dw_xpcs_rk *pxpcs)
+{
+ struct device *dev = &pxpcs->pdev->dev;
+ 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;
+ }
+
+ return 0;
+}
+
+static int xpcs_rk_init_bus(struct dw_xpcs_rk *pxpcs)
+{
+ struct device *dev = &pxpcs->pdev->dev;
+ static atomic_t id = ATOMIC_INIT(-1);
+ struct mii_bus *bus;
+ int ret;
+
+ bus = devm_mdiobus_alloc_size(dev, 0);
+ if (!bus)
+ return -ENOMEM;
+
+ bus->name = "Rockchip DW XPCS MCI/APB3";
+ bus->read = xpcs_rk_read_c22;
+ bus->write = xpcs_rk_write_c22;
+ bus->read_c45 = xpcs_rk_read_c45;
+ bus->write_c45 = xpcs_rk_write_c45;
+ bus->phy_mask = ~0;
+ bus->parent = dev;
+ bus->priv = pxpcs;
+
+ snprintf(bus->id, MII_BUS_ID_SIZE,
+ "rockchip_dwxpcs-%x", atomic_inc_return(&id));
+
+ /*
+ * MDIO-bus here serves as just a back-end engine abstracting out
+ * the MDIO and MCI/APB3 IO interfaces utilized for the Rockchip DWXPCS CSRs
+ * access.
+ */
+ ret = devm_mdiobus_register(dev, bus);
+ if (ret) {
+ dev_err(dev, "Failed to create MDIO bus\n");
+ return ret;
+ }
+
+ pxpcs->bus = bus;
+ return 0;
+}
+
+static int xpcs_rk_probe(struct platform_device *pdev)
+{
+ struct dw_xpcs_rk *pxpcs;
+ int ret;
+
+ pxpcs = xpcs_rk_create_data(pdev);
+ if (IS_ERR(pxpcs))
+ return PTR_ERR(pxpcs);
+
+ /*
+ * The XPCS may be attached to a power domain (e.g. PD_PIPE). The domain
+ * must be powered on before any register access, otherwise the SoC will
+ * trigger a synchronous external abort (SError).
+ *
+ * Accessing the XPCS registers also requires a TX clock from the SerDes,
+ * which is needed for the soft reset.
+ */
+ ret = xpcs_rk_serdes_phy_init(pxpcs);
+ if (ret)
+ return ret;
+
+ ret = xpcs_rk_serdes_phy_poweron(pxpcs);
+ if (ret)
+ return ret;
+
+ ret = xpcs_rk_init_res(pxpcs);
+ if (ret)
+ return ret;
+
+ ret = xpcs_rk_init_clk(pxpcs);
+ if (ret)
+ return ret;
+
+ ret = xpcs_rk_init_bus(pxpcs);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct of_device_id xpcs_rk_of_ids[] = {
+ { .compatible = "rockchip,rk3568-xpcs" },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, xpcs_rk_of_ids);
+
+struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np)
+{
+ struct platform_device *pdev;
+ struct device_node *pcs_np;
+ struct dw_xpcs_rk *pxpcs;
+ struct dw_xpcs *xpcs;
+ u32 port;
+
+ if (!of_device_is_available(np))
+ return ERR_PTR(-ENODEV);
+
+ if (of_property_read_u32(np, "reg", &port))
+ return ERR_PTR(-EINVAL);
+
+ if (!xpcs_rk_mdio_addr_validate((int)port))
+ return ERR_PTR(-EINVAL);
+
+ /* The XPCS pdev is attached to the parent node */
+ pcs_np = of_get_parent(np);
+ if (!pcs_np)
+ return ERR_PTR(-ENODEV);
+
+ if (!of_device_is_available(pcs_np)) {
+ of_node_put(pcs_np);
+ return ERR_PTR(-ENODEV);
+ }
+
+ if (!of_match_node(xpcs_rk_of_ids, pcs_np)) {
+ of_node_put(pcs_np);
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdev = of_find_device_by_node(pcs_np);
+ of_node_put(pcs_np);
+ if (!pdev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ device_lock(&pdev->dev);
+ pxpcs = platform_get_drvdata(pdev);
+ if (!pxpcs || !pxpcs->bus) {
+ device_unlock(&pdev->dev);
+ put_device(&pdev->dev);
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ xpcs = xpcs_create_mdiodev(pxpcs->bus, (int)port);
+ device_unlock(&pdev->dev);
+ if (IS_ERR(xpcs)) {
+ put_device(&pdev->dev);
+ return xpcs;
+ }
+
+ if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) {
+ xpcs_destroy(xpcs);
+ put_device(&pdev->dev);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ put_device(&pdev->dev);
+ return xpcs;
+}
+EXPORT_SYMBOL_GPL(xpcs_rk_create);
+
+static int xpcs_rk_pm_runtime_suspend(struct device *dev)
+{
+ struct dw_xpcs_rk *pxpcs = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(pxpcs->csr_clk);
+
+ return 0;
+}
+
+static int xpcs_rk_pm_runtime_resume(struct device *dev)
+{
+ struct dw_xpcs_rk *pxpcs = dev_get_drvdata(dev);
+
+ return clk_prepare_enable(pxpcs->csr_clk);
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(xpcs_rk_pm_ops,
+ xpcs_rk_pm_runtime_suspend,
+ xpcs_rk_pm_runtime_resume,
+ NULL);
+
+static struct platform_driver xpcs_rk_driver = {
+ .probe = xpcs_rk_probe,
+ .driver = {
+ .name = "rk_xpcs-dwxpcs",
+ .pm = pm_ptr(&xpcs_rk_pm_ops),
+ .of_match_table = xpcs_rk_of_ids,
+ },
+};
+module_platform_driver(xpcs_rk_driver);
+
+MODULE_DESCRIPTION("Rockchip XPCS platform device driver");
+MODULE_AUTHOR("Coia Prant <coiaprant@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/pcs/pcs-xpcs-rk.h b/include/linux/pcs/pcs-xpcs-rk.h
new file mode 100644
index 0000000000000..28723d5bd75cc
--- /dev/null
+++ b/include/linux/pcs/pcs-xpcs-rk.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __LINUX_PCS_XPCS_ROCKCHIP_H
+#define __LINUX_PCS_XPCS_ROCKCHIP_H
+
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/pcs/pcs-xpcs.h>
+
+struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np);
+
+#endif /* __LINUX_PCS_XPCS_ROCKCHIP_H */
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 07/10] net: pcs: xpcs: add Rockchip RK3568 platform glue driver
2026-09-07 15:37 ` [PATCH net-next v4 07/10] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
@ 2026-09-12 20:13 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:13 UTC (permalink / raw)
To: coiaprant
Cc: Jakub Kicinski, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, vkoul, maxime.chevallier,
mcoquelin.stm32, alexandre.torgue, prabhakar.mahadev-lad.rj,
romain.gantois, hkallweit1, neil.armstrong, linux, shawn.lin,
david, netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: pcs: xpcs: add Rockchip RK3568 platform glue driver
This adds a glue driver for the Synopsys DesignWare XPCS found on RK3568,
which is reached through APB3 memory-mapped registers instead of a real
MDIO bus. The driver registers a virtual MDIO bus that remaps the four
MII ports onto their respective MMDs, and exports xpcs_rk_create() so a
MAC driver can obtain a dw_xpcs instance.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part2%20V1.1-20210301.pdf (Page 2078)
A few questions below.
> diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
> index e417fd66f660a..88bc40791e69f 100644
> --- a/drivers/net/pcs/Kconfig
> +++ b/drivers/net/pcs/Kconfig
> @@ -12,6 +12,29 @@ 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.
[Severity: Medium]
Should this new user-visible symbol be part of a patch whose changelog only
describes adding Rockchip RK3568 support?
Two side effects of introducing PCS_XPCS_PLATFORM are not mentioned
anywhere in the commit message:
- the "snps,dw-xpcs" platform glue moves out of pcs_xpcs.ko into a new
pcs-xpcs-plat.ko, which changes module packaging for every existing
memory-mapped DW XPCS user
- the glue becomes optional, so a config with PCS_XPCS=y and
PCS_XPCS_PLATFORM=n silently loses "snps,dw-xpcs" support
Since this restructuring of shared code is independent of the Rockchip
glue, could it be split into its own preparatory patch with the effects
described?
> +
> +config PCS_XPCS_ROCKCHIP
> + tristate "Rockchip XPCS controller support"
> + default ARCH_ROCKCHIP
> + depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
[Severity: Medium]
Do the dependencies here need GENERIC_PHY and PM_GENERIC_DOMAINS as well?
xpcs_rk_probe() cannot make forward progress without either of them. With
CONFIG_GENERIC_PHY=n, the first probe step hits the stub:
include/linux/phy/phy.h:
static inline struct phy *devm_phy_get(struct device *dev,
const char *string)
{
return ERR_PTR(-ENOSYS);
}
so xpcs_rk_serdes_phy_init() always fails through dev_err_probe().
With CONFIG_PM_GENERIC_DOMAINS=n (which covers every CONFIG_PM=n build),
xpcs_rk_serdes_phy_poweron() hits:
include/linux/pm_domain.h:
static inline int dev_pm_genpd_rpm_always_on(struct device *dev, bool on)
{
return -EOPNOTSUPP;
}
and the driver treats that as fatal. Since the symbol is also
default ARCH_ROCKCHIP, it can be built and matched in configurations where
it can never bind. Would adding depends on GENERIC_PHY and
PM_GENERIC_DOMAINS be appropriate?
> + 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..68bcaa1a5cbbf
> --- /dev/null
> +++ b/drivers/net/pcs/pcs-xpcs-rk.c
> @@ -0,0 +1,543 @@
[ ... ]
> +static int xpcs_rk_serdes_phy_init(struct dw_xpcs_rk *pxpcs)
> +{
> + struct device *dev = &pxpcs->pdev->dev;
> +
> + pxpcs->serdes_phy = devm_phy_get(dev, "serdes");
> + if (IS_ERR(pxpcs->serdes_phy))
> + return dev_err_probe(dev, PTR_ERR(pxpcs->serdes_phy),
> + "Failed to get SerDes PHY\n");
> +
> + return 0;
> +}
[Severity: Low]
Is the mandatory "serdes" PHY consistent with the device tree added by the
rest of the series?
The binding added in this series lists phys and phy-names under required:
Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml:
required:
- compatible
- reg
...
- phys
- phy-names
- power-domains
but the SoC node has neither property:
arch/arm64/boot/dts/rockchip/rk3568.dtsi:
xpcs: pcs@fda00000 {
compatible = "rockchip,rk3568-xpcs";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0xfda00000 0x0 0x200000>;
clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
clock-names = "csr", "eee";
power-domains = <&power RK3568_PD_PIPE>;
status = "disabled";
dtschema validates nodes regardless of status, so every RK3568 DTB other
than the one board patched later in the series should report that phys is
a required property. Any board that enables the node without adding
phys/phy-names also gets an unconditional probe failure here, since there
is no devm_phy_get_optional() path. Should the PHY properties move to the
SoC dtsi, or should they be dropped from required:?
[ ... ]
> +static int xpcs_rk_init_clk(struct dw_xpcs_rk *pxpcs)
> +{
> + struct device *dev = &pxpcs->pdev->dev;
> + 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;
> + }
[Severity: Low]
The EEE clock is acquired and permanently enabled, but nothing ever
derives the XPCS 100 ns multiplier from its rate. Should this driver call
xpcs_config_eee_mult_fact() the way dwmac-intel.c does?
Without it xpcs->eee_mult_fact stays 0, and the core programs that value
verbatim when EEE is turned on:
drivers/net/pcs/pcs-xpcs.c:
/* Configure the eee_mult_fact so that
* clk_eee_time_period * (mult_fact + 1) is within the range
* 80 to 120ns.
*/
val = ... FIELD_PREP(DW_VR_MII_EEE_MULT_FACT_100NS,
xpcs->eee_mult_fact);
With the RK3568 EEE clock parents (125/200 MHz, so a 5-8 ns period), a
multiplier of 0 puts the LPI timing well below that range. The register
reset value is also 0, so nothing valid is being overwritten, but is the
timing expected to be programmed here?
[ ... ]
> +struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np)
> +{
[ ... ]
> + pdev = of_find_device_by_node(pcs_np);
> + of_node_put(pcs_np);
> + if (!pdev)
> + return ERR_PTR(-EPROBE_DEFER);
> +
> + device_lock(&pdev->dev);
> + pxpcs = platform_get_drvdata(pdev);
> + if (!pxpcs || !pxpcs->bus) {
> + device_unlock(&pdev->dev);
> + put_device(&pdev->dev);
> + return ERR_PTR(-EPROBE_DEFER);
> + }
> +
> + xpcs = xpcs_create_mdiodev(pxpcs->bus, (int)port);
> + device_unlock(&pdev->dev);
> + if (IS_ERR(xpcs)) {
> + put_device(&pdev->dev);
> + return xpcs;
> + }
> +
> + if (!device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_CONSUMER)) {
[Severity: Medium]
Can the mii_bus be freed under the new dw_xpcs in the window between
device_unlock() and device_link_add()?
The bus is devm managed, so it goes away when the XPCS platform driver
unbinds:
xpcs_rk_init_bus()
bus = devm_mdiobus_alloc_size(dev, 0);
...
ret = devm_mdiobus_register(dev, bus);
and xpcs_create_mdiodev() takes no reference on it:
drivers/net/phy/mdio_device.c:mdio_device_create()
mdiodev->bus = bus;
mdiodev->addr = addr;
so the managed device link is the only thing keeping the supplier binding
alive. With the lock already dropped, an unbind through sysfs or an
rmmod of pcs-xpcs-rk can run device_release_driver() before the link
exists. put_device() keeps the struct device around but not the driver
binding or its devm resources, leaving mdiodev->bus and the ioremapped
window pointing at freed memory. The xpcs_destroy() call on the
device_link_add() failure path would then touch it too.
Would creating the device link while still holding device_lock(), or
otherwise pinning the supplier before unlocking, close this?
[ ... ]
> +EXPORT_SYMBOL_GPL(xpcs_rk_create);
[Severity: Low]
As of this commit alone, xpcs_rk_create() has no in-tree caller, so the
driver only probes, pins the power domain and SerDes, and registers the
virtual MDIO bus without ever producing the phylink_pcs the changelog
describes.
This resolves later in the same series: rk_pcs_init() in
drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c calls
xpcs_rk_create(priv->device, pcs_node), rk_gmac_probe() installs
pcs_init/pcs_exit/select_pcs, and DWMAC_ROCKCHIP gains
select PCS_XPCS_ROCKCHIP. Noting it only in case the ordering was
intentional.
[ ... ]
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next v4 08/10] net: stmmac: dwmac-rk: add SGMII support for RK3568
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (6 preceding siblings ...)
2026-09-07 15:37 ` [PATCH net-next v4 07/10] net: pcs: xpcs: add Rockchip RK3568 platform glue driver Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:13 ` Jakub Kicinski
2026-09-07 15:37 ` [PATCH net-next v4 09/10] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port Coia Prant
` (2 subsequent siblings)
10 siblings, 1 reply; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The RK3568 SoC integrates a Synopsys DesignWare XPCS that can be
connected to GMAC0 or GMAC1 in SGMII mode. Add the necessary glue
logic to support this configuration.
The current dwmac-rk driver does not support SGMII mode. SGMII
requires a PCS to handle auto-negotiation and link state reporting,
but the existing driver only supports RGMII and RMII.
Add a set_to_sgmii() callback to configure the GMAC GRF register for
SGMII mode (bit 7 set, interface selection bits 4:6 ignored when set).
Also add a supports_sgmii flag to indicate SGMII capability.
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.
While at it, fix the clock enable ordering in rk_gmac_powerup():
gmac_clk_enable() is now called before any register access, including
the SGMII mode setup path. Previously SGMII mode would bypass the
clock enable via a goto, which could cause synchronous external abort
when accessing MAC registers with the clock domain disabled.
Also clean up the error handling in rk_gmac_powerup() by using a
unified clk_disable label, and add error handling for the default
(unhandled interface) case.
SGMII In-band vs Out-of-band
============================
On RK3568, the MAC clock is fixed at 125 MHz and cannot be dynamically
changed by the stmmac core's set_clk_tx_rate callback. In-band mode
works because the PCS handles rate adaptation internally. Out-of-band
mode does not work because the MAC would need to change the clock rate
to 125/12.5/1.25 MHz for 1000/100/10 Mbps respectively, and the clock
is fixed.
Enable default_an_inband for SGMII and disable the generic stmmac
set_clk_tx_rate callback. This forces phylink to use in-band mode,
where the PCS is responsible for speed/duplex negotiation. Without
this, the stmmac core would attempt to change the clock rate on speed
changes, causing TX to work but RX to fail.
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 | 121 +++++++++++++++---
2 files changed, 104 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/stmicro/stmmac/Kconfig b/drivers/net/ethernet/stmicro/stmmac/Kconfig
index e3dd5adda5aca..5088acc06982e 100644
--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig
+++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig
@@ -170,6 +170,7 @@ config DWMAC_ROCKCHIP
default ARCH_ROCKCHIP
depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
select MFD_SYSCON
+ select PCS_XPCS_ROCKCHIP
help
Support for Ethernet controller on Rockchip RK3288 SoC.
diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
index 8d7042e689261..e47ca1bec5b8b 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)
@@ -851,18 +857,32 @@ 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_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,
+ .supports_sgmii = true,
.regs_valid = true,
.regs = {
@@ -1208,6 +1228,43 @@ static void rk_phy_powerdown(struct rk_priv_data *bsp_priv)
dev_err(bsp_priv->dev, "fail to disable phy-supply\n");
}
+static int rk_pcs_init(struct stmmac_priv *priv)
+{
+ struct device_node *np = priv->device->of_node;
+ struct device_node *pcs_node;
+ struct dw_xpcs *xpcs;
+
+ pcs_node = of_parse_phandle(np, "pcs-handle", 0);
+ if (!pcs_node)
+ return -ENODEV;
+
+ xpcs = xpcs_rk_create(priv->device, pcs_node);
+ of_node_put(pcs_node);
+ if (IS_ERR(xpcs))
+ return PTR_ERR(xpcs);
+
+ priv->hw->xpcs = xpcs;
+ return 0;
+}
+
+static void rk_pcs_exit(struct stmmac_priv *priv)
+{
+ if (!priv->hw->xpcs)
+ return;
+
+ xpcs_destroy(priv->hw->xpcs);
+ priv->hw->xpcs = NULL;
+}
+
+static struct phylink_pcs *rk_select_pcs(struct stmmac_priv *priv,
+ phy_interface_t interface)
+{
+ if (!priv->hw->xpcs)
+ return NULL;
+
+ return xpcs_to_phylink_pcs(priv->hw->xpcs);
+}
+
static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev,
struct plat_stmmacenet_data *plat,
const struct rk_gmac_ops *ops)
@@ -1330,6 +1387,7 @@ static struct rk_priv_data *rk_gmac_setup(struct platform_device *pdev,
bsp_priv->supports_rgmii = ops->supports_rgmii || !!ops->set_to_rgmii;
bsp_priv->supports_rmii = ops->supports_rmii || !!ops->set_to_rmii;
+ bsp_priv->supports_sgmii = ops->supports_sgmii || !!ops->set_to_sgmii;
if (ops->init) {
ret = ops->init(bsp_priv);
@@ -1361,6 +1419,10 @@ static int rk_gmac_check_ops(struct rk_priv_data *bsp_priv)
if (!bsp_priv->supports_rmii)
return -EINVAL;
break;
+ case PHY_INTERFACE_MODE_SGMII:
+ if (!bsp_priv->supports_sgmii)
+ return -EINVAL;
+ break;
default:
dev_err(bsp_priv->dev,
"unsupported interface %d", bsp_priv->phy_iface);
@@ -1379,16 +1441,19 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
if (ret)
return ret;
+ ret = gmac_clk_enable(bsp_priv, true);
+ if (ret)
+ return ret;
+
+ if (bsp_priv->phy_iface == PHY_INTERFACE_MODE_SGMII)
+ goto set_mode;
+
ret = rk_get_phy_intf_sel(bsp_priv->phy_iface);
if (ret < 0)
- return ret;
+ goto clk_disable;
intf = ret;
- ret = gmac_clk_enable(bsp_priv, true);
- if (ret)
- return ret;
-
if (bsp_priv->gmac_phy_intf_sel_mask ||
bsp_priv->gmac_rmii_mode_mask) {
/* If defined, encode the phy_intf_sel value */
@@ -1399,10 +1464,8 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
bsp_priv->gmac_rmii_mode_mask);
ret = rk_write_gmac_grf_reg(bsp_priv, val);
- if (ret < 0) {
- gmac_clk_enable(bsp_priv, false);
- return ret;
- }
+ if (ret < 0)
+ goto clk_disable;
}
if (bsp_priv->clock.rmii_mode_mask) {
@@ -1410,13 +1473,12 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
bsp_priv->clock.rmii_mode_mask);
ret = rk_write_clock_grf_reg(bsp_priv, val);
- if (ret < 0) {
- gmac_clk_enable(bsp_priv, false);
- return ret;
- }
+ if (ret < 0)
+ goto clk_disable;
}
- /*rmii or rgmii*/
+set_mode:
+ /* rmii, rgmii, sgmii */
switch (bsp_priv->phy_iface) {
case PHY_INTERFACE_MODE_RGMII:
dev_info(dev, "init for RGMII\n");
@@ -1447,15 +1509,20 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
if (bsp_priv->ops->set_to_rmii)
bsp_priv->ops->set_to_rmii(bsp_priv);
break;
+ case PHY_INTERFACE_MODE_SGMII:
+ dev_info(dev, "init for SGMII\n");
+ if (bsp_priv->ops->set_to_sgmii)
+ bsp_priv->ops->set_to_sgmii(bsp_priv);
+ break;
default:
dev_err(dev, "NO interface defined!\n");
+ ret = -EINVAL;
+ goto clk_disable;
}
ret = rk_phy_powerup(bsp_priv);
- if (ret) {
- gmac_clk_enable(bsp_priv, false);
- return ret;
- }
+ if (ret)
+ goto clk_disable;
pm_runtime_get_sync(dev);
@@ -1463,6 +1530,10 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
bsp_priv->ops->integrated_phy_powerup(bsp_priv);
return 0;
+
+clk_disable:
+ gmac_clk_enable(bsp_priv, false);
+ return ret;
}
static void rk_gmac_powerdown(struct rk_priv_data *gmac)
@@ -1486,6 +1557,9 @@ static void rk_get_interfaces(struct stmmac_priv *priv, void *bsp_priv,
if (rk->supports_rmii)
__set_bit(PHY_INTERFACE_MODE_RMII, interfaces);
+
+ if (rk->supports_sgmii)
+ __set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);
}
static int rk_set_clk_tx_rate(void *bsp_priv_, struct clk *clk_tx_i,
@@ -1602,6 +1676,17 @@ static int rk_gmac_probe(struct platform_device *pdev)
plat_dat->suspend = rk_gmac_suspend;
plat_dat->resume = rk_gmac_resume;
+ if (plat_dat->phy_interface == PHY_INTERFACE_MODE_SGMII) {
+ /* SGMII clock always runs at 125 MHz */
+ plat_dat->set_clk_tx_rate = NULL;
+
+ /* SGMII requires a PCS */
+ plat_dat->default_an_inband = true;
+ plat_dat->pcs_init = rk_pcs_init;
+ plat_dat->pcs_exit = rk_pcs_exit;
+ plat_dat->select_pcs = rk_select_pcs;
+ }
+
plat_dat->bsp_priv = rk_gmac_setup(pdev, plat_dat, data);
if (IS_ERR(plat_dat->bsp_priv))
return PTR_ERR(plat_dat->bsp_priv);
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 08/10] net: stmmac: dwmac-rk: add SGMII support for RK3568
2026-09-07 15:37 ` [PATCH net-next v4 08/10] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
@ 2026-09-12 20:13 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:13 UTC (permalink / raw)
To: coiaprant
Cc: Jakub Kicinski, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, vkoul, maxime.chevallier,
mcoquelin.stm32, alexandre.torgue, prabhakar.mahadev-lad.rj,
romain.gantois, hkallweit1, neil.armstrong, linux, shawn.lin,
david, netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
net: stmmac: dwmac-rk: add SGMII support for RK3568
This adds SGMII glue for RK3568: a set_to_sgmii() callback that programs
GMAC CON1 bit 7, pcs_init/pcs_exit/select_pcs callbacks that create the
Rockchip XPCS from the "pcs-handle" phandle, and a rework of the
rk_gmac_powerup() error unwinding into a single clk_disable label.
Link: https://dl.radxa.com/rock3/docs/hw/datasheet/Rockchip%20RK3568%20TRM%20Part1%20V1.1-20210301.pdf (Page 386)
A few questions below.
> While at it, fix the clock enable ordering in rk_gmac_powerup():
> gmac_clk_enable() is now called before any register access, including
> the SGMII mode setup path. Previously SGMII mode would bypass the
> clock enable via a goto, which could cause synchronous external abort
> when accessing MAC registers with the clock domain disabled.
[Severity: Low]
Does this paragraph describe the baseline? At 879e280b8486 there is no
SGMII case and no goto in rk_gmac_powerup(), and the order is already:
ret = rk_get_phy_intf_sel(bsp_priv->phy_iface);
if (ret < 0)
return ret;
intf = ret;
ret = gmac_clk_enable(bsp_priv, true);
if (ret)
return ret;
...
ret = rk_write_gmac_grf_reg(bsp_priv, val);
so gmac_clk_enable() already preceded every register access and there was
nothing that could bypass it. The change here is a pure reorder ahead of
rk_get_phy_intf_sel(), which touches no registers, plus the new unified
unwind label.
Would it be more accurate to describe this as preparing the ordering for
the new SGMII path rather than as a fix for a synchronous external abort?
> diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
> index 8d7042e689261..e47ca1bec5b8b 100644
> --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
> +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c
[ ... ]
> @@ -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)
> @@ -851,18 +857,32 @@ 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_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,
> + .supports_sgmii = true,
[Severity: Medium]
Who clears CON1 bit 7 for RMII on rk3568?
rk3568_set_to_rgmii() now explicitly writes RK3568_GMAC_MODE_RMII_RGMII,
which shows bit 7 has to be programmed by the driver. rk3568_ops declares
supports_rmii = true but has no .set_to_rmii, so the RMII branch of
rk_gmac_powerup() is a no-op:
case PHY_INTERFACE_MODE_RMII:
dev_info(dev, "init for RMII\n");
if (bsp_priv->ops->set_to_rmii)
bsp_priv->ops->set_to_rmii(bsp_priv);
break;
and the generic GRF write uses gmac_phy_intf_sel_mask = GENMASK_U16(6, 4),
whose write-enable half-word never covers bit 7.
Since rk3568_set_to_sgmii() leaves bit 7 set persistently and nothing in
rk_gmac_powerdown() restores it, can an RMII port come up muxed to the
SerDes when bit 7 was left set by firmware or by a previous SGMII
configuration of the same MAC (kexec, warm reboot)? The probe would report
success while the link never works.
>
> .regs_valid = true,
> .regs = {
> @@ -1208,6 +1228,43 @@ static void rk_phy_powerdown(struct rk_priv_data *bsp_priv)
> dev_err(bsp_priv->dev, "fail to disable phy-supply\n");
> }
>
> +static int rk_pcs_init(struct stmmac_priv *priv)
> +{
> + struct device_node *np = priv->device->of_node;
> + struct device_node *pcs_node;
> + struct dw_xpcs *xpcs;
> +
> + pcs_node = of_parse_phandle(np, "pcs-handle", 0);
> + if (!pcs_node)
> + return -ENODEV;
[Severity: Low]
Should "pcs-handle" be documented in the Rockchip MAC binding?
This makes the property effectively mandatory for phy-mode = "sgmii" on
RK3568: a missing phandle returns -ENODEV, and stmmac_pcs_setup() turns
that into a probe failure:
drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c:stmmac_pcs_setup() {
ret = priv->plat->pcs_init(priv);
if (ret)
return dev_err_probe(priv->device, ret, "No xPCS found\n");
}
Documentation/devicetree/bindings/net/rockchip-dwmac.yaml does not mention
pcs-handle at all - it is neither described nor conditionally required for
the SGMII-capable compatibles - while the new
Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml says the
port "is linked to an Ethernet MAC controller via the pcs-handle property
in the MAC's device tree node". Other stmmac glue bindings that parse this
property describe it (renesas,rzn1-gmac.yaml, renesas,rzv2h-gbeth.yaml).
Could the MAC binding be updated to match?
> +
> + xpcs = xpcs_rk_create(priv->device, pcs_node);
> + of_node_put(pcs_node);
> + if (IS_ERR(xpcs))
> + return PTR_ERR(xpcs);
> +
> + priv->hw->xpcs = xpcs;
> + return 0;
> +}
[ ... ]
> @@ -1330,6 +1387,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;
[ ... ]
> @@ -1379,16 +1441,19 @@ static int rk_gmac_powerup(struct rk_priv_data *bsp_priv)
> if (ret)
> return ret;
>
> + ret = gmac_clk_enable(bsp_priv, true);
> + if (ret)
> + return ret;
[Severity: Medium]
This isn't a bug introduced by this patch, but the bare return here cannot
unwind a partially enabled clock domain. gmac_clk_enable() leaks the bulk
clock references when the PHY clock fails:
drivers/net/ethernet/stmicro/stmmac/dwmac-rk.c:gmac_clk_enable() {
ret = clk_bulk_prepare_enable(bsp_priv->num_clks,
bsp_priv->clks);
if (ret)
return ret;
ret = clk_prepare_enable(bsp_priv->clk_phy);
if (ret)
return ret;
...
bsp_priv->clk_enabled = true;
}
The second failure path returns without clk_bulk_disable_unprepare() and
without setting clk_enabled, so the new clk_disable label,
rk_gmac_powerdown() and the suspend path are all no-ops afterwards because
the disable branch is gated on clk_enabled. Does this leave the bulk
clocks prepared and enabled for the lifetime of the system?
> +
> + 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;
>
[ ... ]
> @@ -1447,15 +1509,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;
> }
[ ... ]
> @@ -1486,6 +1557,9 @@ static void rk_get_interfaces(struct stmmac_priv *priv, void *bsp_priv,
>
> if (rk->supports_rmii)
> __set_bit(PHY_INTERFACE_MODE_RMII, interfaces);
> +
> + if (rk->supports_sgmii)
> + __set_bit(PHY_INTERFACE_MODE_SGMII, interfaces);
> }
[Severity: Medium]
Is the SGMII advertisement here consistent with where the PCS is installed?
supports_sgmii is true for every rk3568 instance regardless of DT, so SGMII
always lands in phylink_config.supported_interfaces. The PCS plumbing,
however, is only installed when the DT phy-mode is exactly SGMII (see the
rk_gmac_probe() hunk below).
On an RK3568 board with phy-mode = "rgmii"/"rmii", phylink can still pick
SGMII - phylink_validate_phy() intersects phy->possible_interfaces with
config->supported_interfaces - and then:
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_mac_select_pcs() {
if (priv->plat->select_pcs) {
pcs = priv->plat->select_pcs(priv, interface);
if (!IS_ERR(pcs))
return pcs;
}
}
returns NULL because select_pcs is unset, no XPCS was ever created
(pcs_init is NULL), and rk_gmac_powerup() has already programmed the GRF
once from the DT-derived bsp_priv->phy_iface, so rk3568_set_to_sgmii() never
ran. Would gating the SGMII bit on the same condition that installs
select_pcs/pcs_init keep the two in agreement?
>
> static int rk_set_clk_tx_rate(void *bsp_priv_, struct clk *clk_tx_i,
> @@ -1602,6 +1676,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]
Does default_an_inband guarantee in-band mode here?
include/linux/phylink.h describes it as a default that "a fixed-link
specification will override", and phylink has two ways out of in-band:
drivers/net/phy/phylink.c:phylink_parse_mode() {
dn = fwnode_get_named_child_node(fwnode, "fixed-link");
if (dn || fwnode_property_present(fwnode, "fixed-link"))
pl->cfg_link_an_mode = MLO_AN_FIXED;
}
drivers/net/phy/phylink.c:phylink_pcs_neg_mode() {
neg_mode = PHYLINK_PCS_NEG_OUTBAND;
if (pl->phydev)
mode = MLO_AN_PHY;
}
With set_clk_tx_rate cleared, stmmac_mac_link_up() then programs no rate at
all:
drivers/net/ethernet/stmicro/stmmac/stmmac_main.c:stmmac_mac_link_up() {
if (priv->plat->set_clk_tx_rate) {
ret = priv->plat->set_clk_tx_rate(priv->plat->bsp_priv,
priv->plat->clk_tx_i,
interface, speed);
}
So for phy-mode = "sgmii" with a fixed-link node, or with a PHY that cannot
do in-band signalling, the fixed 125 MHz clock is used at 10/100 Mbps -
which the commit message says gives working TX and failing RX. Should the
driver reject out-of-band SGMII explicitly instead of relying on the
default?
> + plat_dat->pcs_init = rk_pcs_init;
> + plat_dat->pcs_exit = rk_pcs_exit;
> + plat_dat->select_pcs = rk_select_pcs;
> + }
> +
[ ... ]
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next v4 09/10] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (7 preceding siblings ...)
2026-09-07 15:37 ` [PATCH net-next v4 08/10] net: stmmac: dwmac-rk: add SGMII support for RK3568 Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:13 ` Jakub Kicinski
2026-09-07 15:37 ` [PATCH net-next v4 10/10] MAINTAINERS: add entry for Rockchip XPCS driver Coia Prant
2026-09-12 20:14 ` [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Jakub Kicinski
10 siblings, 1 reply; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
The Ariaboard Photonicat has a Motorcomm YT8521SC Gigabit Ethernet PHY
connected to GMAC0 via XPCS SGMII. Enable the necessary nodes to make
this port functional.
Enable combphy2 with rockchip,sgmii-mac-sel = <0> to route the SGMII
interface to GMAC0. Enable the xpcs node and its port 0 sub-node,
referencing combphy2 as the SerDes PHY.
Add the mdio0 node with the YT8521SC PHY at address 3, including its
reset GPIO and LED configuration. Also add LED configuration for the
existing RGMII PHY on mdio1 for consistency.
Signed-off-by: Coia Prant <coiaprant@gmail.com>
---
.../boot/dts/rockchip/rk3568-photonicat.dts | 74 ++++++++++++++++++-
1 file changed, 72 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts b/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
index 58c1052ba8ef3..25caa44198843 100644
--- a/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
@@ -3,6 +3,7 @@
/dts-v1/;
#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/leds/common.h>
#include <dt-bindings/pinctrl/rockchip.h>
#include <dt-bindings/soc/rockchip,vop2.h>
#include "rk3568.dtsi"
@@ -242,6 +243,7 @@ &combphy1 {
&combphy2 {
status = "okay";
+ rockchip,sgmii-mac-sel = <0>;
};
&cpu0 {
@@ -260,9 +262,18 @@ &cpu3 {
cpu-supply = <&vdd_cpu>;
};
-/* Motorcomm YT8521SC LAN port (require SGMII) */
+/* Motorcomm YT8521SC LAN port */
&gmac0 {
- status = "disabled";
+ assigned-clocks = <&cru SCLK_GMAC0_RX_TX>;
+ assigned-clock-parents = <&xpcs_gmac0_clk>;
+ pcs-handle = <&xpcs_mii0>;
+ managed = "in-band-status";
+ phy-handle = <&sgmii_phy>;
+ phy-mode = "sgmii";
+ phy-supply = <&vcc_3v3>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&gmac0_miim>;
+ status = "okay";
};
/* Motorcomm YT8521SC WAN port */
@@ -341,6 +352,36 @@ &i2s0_8ch {
status = "okay";
};
+&mdio0 {
+ sgmii_phy: ethernet-phy@3 {
+ compatible = "ethernet-phy-ieee802.3-c22";
+ reg = <0x3>;
+ max-speed = <1000>;
+ reset-assert-us = <20000>;
+ reset-deassert-us = <100000>;
+ reset-gpios = <&gpio3 RK_PC6 GPIO_ACTIVE_LOW>;
+
+ leds {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@1 {
+ reg = <1>;
+ color = <LED_COLOR_ID_AMBER>;
+ function = LED_FUNCTION_LAN;
+ default-state = "keep";
+ };
+
+ led@2 {
+ reg = <2>;
+ color = <LED_COLOR_ID_GREEN>;
+ function = LED_FUNCTION_LAN;
+ default-state = "keep";
+ };
+ };
+ };
+};
+
&mdio1 {
rgmii_phy: ethernet-phy@3 {
compatible = "ethernet-phy-ieee802.3-c22";
@@ -350,6 +391,25 @@ rgmii_phy: ethernet-phy@3 {
reset-gpios = <&gpio4 RK_PC0 GPIO_ACTIVE_LOW>;
rx-internal-delay-ps = <1500>;
tx-internal-delay-ps = <1500>;
+
+ leds {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@1 {
+ reg = <1>;
+ color = <LED_COLOR_ID_AMBER>;
+ function = LED_FUNCTION_WAN;
+ default-state = "keep";
+ };
+
+ led@2 {
+ reg = <2>;
+ color = <LED_COLOR_ID_GREEN>;
+ function = LED_FUNCTION_WAN;
+ default-state = "keep";
+ };
+ };
};
};
@@ -586,3 +646,13 @@ &xin32k {
pinctrl-names = "default";
pinctrl-0 = <&clk32k_out1>;
};
+
+&xpcs {
+ status = "okay";
+ phys = <&combphy2 PHY_TYPE_SGMII>;
+ phy-names = "serdes";
+};
+
+&xpcs_mii0 {
+ status = "okay";
+};
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 09/10] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port
2026-09-07 15:37 ` [PATCH net-next v4 09/10] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port Coia Prant
@ 2026-09-12 20:13 ` Jakub Kicinski
0 siblings, 0 replies; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:13 UTC (permalink / raw)
To: coiaprant
Cc: Jakub Kicinski, andrew+netdev, davem, edumazet, pabeni, robh,
krzk+dt, conor+dt, heiko, vkoul, maxime.chevallier,
mcoquelin.stm32, alexandre.torgue, prabhakar.mahadev-lad.rj,
romain.gantois, hkallweit1, neil.armstrong, linux, shawn.lin,
david, netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port
This enables the GMAC0 SGMII LAN port on the Ariaboard Photonicat by
adding the xpcs/xpcs_mii0 overrides, the combphy2 SGMII MAC selection, and
an mdio0 node with the YT8521SC PHY. It also adds PHY LED descriptions on
both mdio0 and mdio1.
> diff --git a/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts b/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
> index 58c1052ba8ef3..25caa44198843 100644
> --- a/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
> +++ b/arch/arm64/boot/dts/rockchip/rk3568-photonicat.dts
[ ... ]
> @@ -242,6 +243,7 @@ &combphy1 {
>
> &combphy2 {
> status = "okay";
> + rockchip,sgmii-mac-sel = <0>;
> };
[Severity: Low]
The changelog says:
"Enable combphy2 with rockchip,sgmii-mac-sel = <0> to route the SGMII
interface to GMAC0."
Is that description accurate? In the baseline the node already reads:
&combphy2 {
status = "okay";
};
so the only new thing here is the rockchip,sgmii-mac-sel property. Could
the wording be adjusted to say the property is added to an already enabled
combphy2?
[Severity: Low]
This isn't a bug, but the new properties do not follow the ordering used by
the rest of this file, where properties come roughly alphabetically and
status is last. Here rockchip,sgmii-mac-sel lands after status, in &gmac0
below pcs-handle comes before managed, and in the new &xpcs override status
is first with phys/phy-names after it. Compare the existing &gmac1
override, which ends with status = "okay";. Could these be reordered?
>
> &cpu0 {
> @@ -260,9 +262,18 @@ &cpu3 {
> cpu-supply = <&vdd_cpu>;
> };
>
> -/* Motorcomm YT8521SC LAN port (require SGMII) */
> +/* Motorcomm YT8521SC LAN port */
> &gmac0 {
> - status = "disabled";
> + assigned-clocks = <&cru SCLK_GMAC0_RX_TX>;
> + assigned-clock-parents = <&xpcs_gmac0_clk>;
> + pcs-handle = <&xpcs_mii0>;
> + managed = "in-band-status";
> + phy-handle = <&sgmii_phy>;
> + phy-mode = "sgmii";
> + phy-supply = <&vcc_3v3>;
> + pinctrl-names = "default";
> + pinctrl-0 = <&gmac0_miim>;
> + status = "okay";
> };
[ ... ]
> @@ -350,6 +391,25 @@ rgmii_phy: ethernet-phy@3 {
> reset-gpios = <&gpio4 RK_PC0 GPIO_ACTIVE_LOW>;
> rx-internal-delay-ps = <1500>;
> tx-internal-delay-ps = <1500>;
> +
> + leds {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + led@1 {
> + reg = <1>;
> + color = <LED_COLOR_ID_AMBER>;
> + function = LED_FUNCTION_WAN;
> + default-state = "keep";
> + };
> +
> + led@2 {
> + reg = <2>;
> + color = <LED_COLOR_ID_GREEN>;
> + function = LED_FUNCTION_WAN;
> + default-state = "keep";
> + };
> + };
> };
> };
[Severity: Low]
This hunk describes the LEDs of the pre-existing RGMII WAN PHY on &mdio1,
which is already functional and unaffected by the SGMII enablement. The
changelog justifies it only with:
"Also add LED configuration for the existing RGMII PHY on mdio1 for
consistency."
Would it be preferable to split this into its own patch, so this commit
contains only the SGMII LAN port enablement?
[ ... ]
> @@ -586,3 +646,13 @@ &xin32k {
> pinctrl-names = "default";
> pinctrl-0 = <&clk32k_out1>;
> };
> +
> +&xpcs {
> + status = "okay";
> + phys = <&combphy2 PHY_TYPE_SGMII>;
> + phy-names = "serdes";
> +};
[Severity: Medium]
Should phys/phy-names live in rk3568.dtsi rather than in each board file?
The binding lists them as required properties of the XPCS node:
Documentation/devicetree/bindings/net/pcs/rockchip-dwxpcs.yaml
required:
- compatible
- reg
- "#address-cells"
- "#size-cells"
- clocks
- clock-names
- phys
- phy-names
- power-domains
but the SoC node in arch/arm64/boot/dts/rockchip/rk3568.dtsi has neither:
xpcs: pcs@fda00000 {
compatible = "rockchip,rk3568-xpcs";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0xfda00000 0x0 0x200000>;
clocks = <&cru PCLK_XPCS>, <&cru CLK_XPCS_EEE>;
clock-names = "csr", "eee";
power-domains = <&power RK3568_PD_PIPE>;
status = "disabled";
Does this mean every rk3568 dtb reports pcs@fda00000 as missing the required
phys and phy-names under make CHECK_DTBS=y, including boards that do not
enable the XPCS?
The XPCS to combphy2 connection is internal to the SoC, so each board that
enables it will have to repeat the same phandle, as this patch does. Could
phys = <&combphy2 PHY_TYPE_SGMII>; and phy-names = "serdes"; be moved into
the dtsi node instead?
> +
> +&xpcs_mii0 {
> + status = "okay";
> +};
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH net-next v4 10/10] MAINTAINERS: add entry for Rockchip XPCS driver
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (8 preceding siblings ...)
2026-09-07 15:37 ` [PATCH net-next v4 09/10] arm64: dts: rockchip: rk3568-photonicat: enable SGMII LAN port Coia Prant
@ 2026-09-07 15:37 ` Coia Prant
2026-09-12 20:14 ` [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Jakub Kicinski
10 siblings, 0 replies; 21+ messages in thread
From: Coia Prant @ 2026-09-07 15:37 UTC (permalink / raw)
To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Heiko Stuebner, Vinod Koul, Maxime Chevallier, Maxime Coquelin,
Alexandre Torgue, Lad Prabhakar, Romain Gantois, Heiner Kallweit,
Coia Prant
Cc: Neil Armstrong, Russell King, Shawn Lin, David Heidelberg,
netdev, linux-rockchip, devicetree, linux-arm-kernel,
linux-kernel, linux-phy, linux-stm32, linux-renesas-soc
Add 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 fc320b373656f..dda4d211b7db4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23738,6 +23738,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-dwxpcs.yaml
+F: drivers/net/pcs/pcs-xpcs-rk.c
+F: include/linux/pcs/pcs-xpcs-rk.h
+
ROCKER DRIVER
M: Jiri Pirko <jiri@resnulli.us>
L: netdev@vger.kernel.org
--
2.47.3
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS
2026-09-07 15:37 [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Coia Prant
` (9 preceding siblings ...)
2026-09-07 15:37 ` [PATCH net-next v4 10/10] MAINTAINERS: add entry for Rockchip XPCS driver Coia Prant
@ 2026-09-12 20:14 ` Jakub Kicinski
2026-09-12 20:33 ` Coia Prant
10 siblings, 1 reply; 21+ messages in thread
From: Jakub Kicinski @ 2026-09-12 20:14 UTC (permalink / raw)
To: Coia Prant
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Vinod Koul, Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
Lad Prabhakar, Romain Gantois, Heiner Kallweit, Neil Armstrong,
Russell King, Shawn Lin, David Heidelberg, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
On Mon, 7 Sep 2026 23:37:03 +0800 Coia Prant wrote:
> This series adds proper SGMII support for the Rockchip RK3568 SoC
> using the integrated Synopsys DesignWare XPCS, along with necessary
> fixes and refactoring in the stmmac core and XPCS driver.
Sending the AI review FYI, please look thru it and judge if any of the
comments are worth respinning for. We're waiting for a review on the
device tree bindings, anyway.
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS
2026-09-12 20:14 ` [PATCH net-next v4 00/10] net: pcs: add basic support for RK3568 XPCS Jakub Kicinski
@ 2026-09-12 20:33 ` Coia Prant
0 siblings, 0 replies; 21+ messages in thread
From: Coia Prant @ 2026-09-12 20:33 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Andrew Lunn, David S . Miller, Eric Dumazet, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner,
Vinod Koul, Maxime Chevallier, Maxime Coquelin, Alexandre Torgue,
Lad Prabhakar, Romain Gantois, Heiner Kallweit, Neil Armstrong,
Russell King, Shawn Lin, David Heidelberg, netdev,
linux-rockchip, devicetree, linux-arm-kernel, linux-kernel,
linux-phy, linux-stm32, linux-renesas-soc
Jakub Kicinski <kuba@kernel.org> 于2026年9月13日周日 04:14写道:
>
> On Mon, 7 Sep 2026 23:37:03 +0800 Coia Prant wrote:
> > This series adds proper SGMII support for the Rockchip RK3568 SoC
> > using the integrated Synopsys DesignWare XPCS, along with necessary
> > fixes and refactoring in the stmmac core and XPCS driver.
>
> Sending the AI review FYI, please look thru it and judge if any of the
> comments are worth respinning for. We're waiting for a review on the
> device tree bindings, anyway.
I apologize; I have been so busy this week that I’ve repeatedly
overlooked my plan to reply to sashiko-bot's comment. I will get back
to you as soon as possible.
Best,
Coia
^ permalink raw reply [flat|nested] 21+ messages in thread