Thread (29 messages) flat view 29 messages, 2 authors, 4d ago

Re: [PATCH net-next v4 07/10] net: pcs: xpcs: add Rockchip RK3568 platform glue driver

From: Coia Prant <hidden>
Date: 2026-09-13 17:01:08
Also in: linux-arm-kernel, linux-devicetree, linux-phy, linux-renesas-soc, linux-rockchip, lkml

Jakub Kicinski [off-list ref] 于2026年9月13日周日 04:13写道:
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.
quoted
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?
You're right that this restructuring is independent of the Rockchip
glue, but the "silently loses snps,dw-xpcs support" concern is not
accurate: PCS_XPCS_PLATFORM has `default PCS_XPCS`, so when PCS_XPCS
is enabled, the platform glue is also enabled by default. A user would
have to explicitly set CONFIG_PCS_XPCS_PLATFORM=n to lose it.

The module packaging change (snps,dw-xpcs moving from pcs_xpcs.ko to
pcs-xpcs-plat.ko) is real, though. I'll mention it in the changelog.

I'll keep the Kconfig change in this patch since it is small and
directly related to making the Rockchip glue a separate module, but
I'll describe the module packaging side effect explicitly.
quoted
+
+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?
Sure, I will add it.
quoted
+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:?
As replied earlier, the phys/phy-names properties cannot move into
rk3568.dtsi because the SerDes connection is board-specific. Boards that
enable XPCS must provide them.
quoted
+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?
mainline's clk-rk3568.c registers CLK_XPCS_EEE with a parent mux
between gpll200 (200 MHz) and cpll125 (125 MHz), controlled by
CRU_CLKSEL_CON29 bit 13. The reset value is 0 (gpll200, 200 MHz), and
no in-tree DTS changes it, so the default multiplier is 19.

To stay correct if a board changes the mux, I'll derive the value at
runtime in xpcs_rk_init_clk() where the EEE clock is already obtained:

    pxpcs->eee_mult_fact =
        DIV_ROUND_CLOSEST(100 * clk_get_rate(pxpcs->eee_clk),
                          1000000000UL) - 1;

and call xpcs_config_eee_mult_fact() in xpcs_rk_create() right after
xpcs_create_mdiodev() succeeds, since that is where the dw_xpcs
pointer becomes available.

This yields 19 for 200 MHz (100 ns) and 12 for 125 MHz (104 ns), both
within the required 80..120 ns range.
quoted
+struct dw_xpcs *xpcs_rk_create(struct device *dev, struct device_node *np)
+{
[ ... ]
quoted
+     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?
I've addressed this by keeping device_link_add() inside the
device_lock() critical section. device_link_add() uses global locks
(device_links_lock and dpm_list_mtx), not the supplier's device_lock,
so calling it while holding the lock is safe.
quoted
+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.
This ordering is intentional. Patch 7 adds the XPCS glue driver
(provider), and the consumer (rk_pcs_init() in dwmac-rk) is added in
the next patch. This is the standard way to split provider and consumer
patches: each commit compiles on its own, but the full functionality
only comes together at the end of the series.

The exported symbol has no caller until the next patch, which is
expected for this kind of split.

Thanks,
Coia
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help