]> git.ipfire.org Git - thirdparty/openwrt.git/commitdiff
realtek: migrate PCS to fwnode_pcs provider/consumer API
authorJonas Jelonek <jelonek.jonas@gmail.com>
Mon, 25 May 2026 21:58:14 +0000 (21:58 +0000)
committerJonas Jelonek <jelonek.jonas@gmail.com>
Sun, 31 May 2026 10:52:41 +0000 (12:52 +0200)
PCS driver registers each SerDes as an fwnode_pcs provider in probe;
the resolver returns the cached or freshly-allocated rtpcs_link for
the requested (sds, link_idx) cell. DSA glue stops calling
rtpcs_create directly, drops .mac_select_pcs, and instead populates
phylink_config.num_available_pcs / fill_available_pcs from each
port's pcs-handle in phylink_get_caps. The rtl838x_port.pcs pointer
becomes a has_pcs bool populated at port probe via fwnode_property_
present, since nothing assigns the actual phylink_pcs anymore but the
"does this port use a PCS?" checks elsewhere still need a presence
flag.

Without .mac_select_pcs, phylink_major_config only searches the
pcs_list when state->interface is set in phylink_config.pcs_interfaces
(drivers/net/phy/phylink.c:1378). Populate it per port whenever the
port has a pcs-handle, listing the SerDes-routable interface modes for
each SoC variant -- without this, pcs_config / pcs_link_up are never
called and the SerDes is left unconfigured.

pcs_get_state still needs the MAC port number to index per-port link
status registers. Recover it at probe via rtpcs_map_links: walk the
sibling switch's ethernet-ports subtree (same backwards topology
lookup the sibling MDIO driver does for phy-handle), and for every
port whose pcs-handle resolves to one of our SerDes, store the port's
reg in sds->link_port[]. The resolver consults link_port[] when
allocating rtpcs_link and fails with -ENODEV if a consumer requested
a link the map step didn't record. Avoids a driver-side port_base
table that would have to encode per-SoC SerDes-to-port wiring (and
would silently break on non-contiguous variants); the DT is the
single source of truth.

Kconfig selects FWNODE_PCS.

Assisted-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Link: https://github.com/openwrt/openwrt/pull/23539
Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c
target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c
target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h
target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl930x.c
target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl931x.c
target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c
target/linux/realtek/patches-6.18/730-add-pcs-rtl-otto.patch

index 45f645065b958c3ee7c493c2d09195b236575544..a85bf1087fd3590d7570070476e710c3a0bafb61 100644 (file)
@@ -255,8 +255,6 @@ static bool rtldsa_phys_load_deferred(void)
 static int rtl83xx_mdio_probe(struct rtl838x_switch_priv *priv)
 {
        struct device_node *dn, *phy_node, *led_node;
-       struct of_phandle_args pcs_args;
-       bool has_pcs;
        u32 pn;
 
        /* Check if all busses of Realtek mdio controller are registered */
@@ -294,26 +292,14 @@ static int rtl83xx_mdio_probe(struct rtl838x_switch_priv *priv)
                if (of_property_read_u32(dn, "reg", &pn))
                        continue;
 
-               has_pcs = !of_parse_phandle_with_args(dn, "pcs-handle", "#pcs-cells",
-                                                     0, &pcs_args);
                phy_node = of_parse_phandle(dn, "phy-handle", 0);
-               if (pn != priv->r->cpu_port && !phy_node && !has_pcs) {
+               priv->ports[pn].has_pcs = fwnode_property_present(of_fwnode_handle(dn),
+                                                                 "pcs-handle");
+               if (pn != priv->r->cpu_port && !phy_node && !priv->ports[pn].has_pcs) {
                        dev_err(priv->dev, "Port node %d has neither pcs-handle nor phy-handle\n", pn);
                        continue;
                }
 
-               if (has_pcs) {
-                       priv->ports[pn].pcs = rtpcs_create(priv->dev, pcs_args.np,
-                                                          pcs_args.args[0], pn);
-                       of_node_put(pcs_args.np);
-                       if (IS_ERR(priv->ports[pn].pcs)) {
-                               dev_err(priv->dev, "port %u failed to create PCS instance: %ld\n",
-                                       pn, PTR_ERR(priv->ports[pn].pcs));
-                               priv->ports[pn].pcs = NULL;
-                               continue;
-                       }
-               }
-
                priv->ports[pn].leds_on_this_port = 0;
                if (led_node) {
                        if (of_property_read_u32(dn, "led-set", &led_set))
index 7b28c6f4e84fa55e8815a2a3e2e2e7c40a582cdc..194c55a891b74c96871e408ffe8be8a1f280c1ff 100644 (file)
@@ -3,6 +3,7 @@
 #include <net/dsa.h>
 #include <linux/etherdevice.h>
 #include <linux/if_bridge.h>
+#include <linux/pcs/pcs.h>
 #include <asm/mach-rtl-otto/mach-rtl-otto.h>
 
 #include "rtl-otto.h"
@@ -50,7 +51,7 @@ static void rtldsa_enable_phy_polling(struct rtl838x_switch_priv *priv)
        msleep(1000);
        /* Enable all ports with a PHY, including the SFP-ports */
        for (int i = 0; i < priv->r->cpu_port; i++) {
-               if (priv->ports[i].phy || priv->ports[i].pcs)
+               if (priv->ports[i].phy || priv->ports[i].has_pcs)
                        v |= BIT_ULL(i);
        }
 
@@ -180,7 +181,7 @@ static int rtldsa_83xx_setup(struct dsa_switch *ds)
         * they will work in isolated mode (only traffic between port and CPU).
         */
        for (int i = 0; i < priv->r->cpu_port; i++) {
-               if (priv->ports[i].phy || priv->ports[i].pcs) {
+               if (priv->ports[i].phy || priv->ports[i].has_pcs) {
                        priv->ports[i].pm = BIT_ULL(priv->r->cpu_port);
                        priv->r->traffic_set(i, BIT_ULL(i));
                }
@@ -253,7 +254,7 @@ static int rtldsa_93xx_setup(struct dsa_switch *ds)
         * they will work in isolated mode (only traffic between port and CPU).
         */
        for (int i = 0; i < priv->r->cpu_port; i++) {
-               if (priv->ports[i].phy || priv->ports[i].pcs) {
+               if (priv->ports[i].phy || priv->ports[i].has_pcs) {
                        priv->ports[i].pm = BIT_ULL(priv->r->cpu_port);
                        priv->r->traffic_set(i, BIT_ULL(i));
                }
@@ -285,18 +286,21 @@ static int rtldsa_93xx_setup(struct dsa_switch *ds)
        return 0;
 }
 
-static struct phylink_pcs *rtldsa_phylink_mac_select_pcs(struct phylink_config *config,
-                                                        phy_interface_t interface)
+static int rtldsa_phylink_fill_available_pcs(struct phylink_config *config,
+                                            struct phylink_pcs **available_pcs,
+                                            unsigned int num_available_pcs)
 {
        struct dsa_port *dp = dsa_phylink_to_port(config);
-       struct rtl838x_switch_priv *priv = dp->ds->priv;
 
-       return priv->ports[dp->index].pcs;
+       return fwnode_phylink_pcs_parse(of_fwnode_handle(dp->dn),
+                                       available_pcs, &num_available_pcs);
 }
 
 static void rtldsa_83xx_phylink_get_caps(struct dsa_switch *ds, int port,
                                         struct phylink_config *config)
 {
+       struct dsa_port *dp = dsa_to_port(ds, port);
+
        /*
         * TODO: This needs to take into account the MAC to SERDES mapping and the
         * specific SoC capabilities. Right now we just assume all RTL83xx ports
@@ -311,11 +315,21 @@ static void rtldsa_83xx_phylink_get_caps(struct dsa_switch *ds, int port,
        __set_bit(PHY_INTERFACE_MODE_INTERNAL, config->supported_interfaces);
        __set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
        __set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces);
+
+       if (!fwnode_phylink_pcs_parse(of_fwnode_handle(dp->dn), NULL,
+                                     &config->num_available_pcs)) {
+               config->fill_available_pcs = rtldsa_phylink_fill_available_pcs;
+               __set_bit(PHY_INTERFACE_MODE_SGMII, config->pcs_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_QSGMII, config->pcs_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_1000BASEX, config->pcs_interfaces);
+       }
 }
 
 static void rtldsa_93xx_phylink_get_caps(struct dsa_switch *ds, int port,
                                         struct phylink_config *config)
 {
+       struct dsa_port *dp = dsa_to_port(ds, port);
+
        /*
         * TODO: This needs to take into account the MAC to SERDES mapping and the
         * specific SoC capabilities. Right now we just assume all RTL93xx ports
@@ -334,6 +348,18 @@ static void rtldsa_93xx_phylink_get_caps(struct dsa_switch *ds, int port,
        __set_bit(PHY_INTERFACE_MODE_2500BASEX, config->supported_interfaces);
        __set_bit(PHY_INTERFACE_MODE_USXGMII, config->supported_interfaces);
        __set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->supported_interfaces);
+
+       if (!fwnode_phylink_pcs_parse(of_fwnode_handle(dp->dn), NULL,
+                                     &config->num_available_pcs)) {
+               config->fill_available_pcs = rtldsa_phylink_fill_available_pcs;
+               __set_bit(PHY_INTERFACE_MODE_SGMII, config->pcs_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_QSGMII, config->pcs_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_1000BASEX, config->pcs_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_2500BASEX, config->pcs_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_USXGMII, config->pcs_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_10GBASER, config->pcs_interfaces);
+               __set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->pcs_interfaces);
+       }
 }
 
 static void rtldsa_83xx_phylink_mac_config(struct phylink_config *config,
@@ -776,7 +802,7 @@ static void rtldsa_poll_counters(struct work_struct *work)
                                                        counters_work);
 
        for (int port = 0; port < priv->r->cpu_port; port++) {
-               if (!priv->ports[port].phy && !priv->ports[port].pcs)
+               if (!priv->ports[port].phy && !priv->ports[port].has_pcs)
                        continue;
 
                rtldsa_counters_lock(priv, port);
@@ -793,7 +819,7 @@ static void rtldsa_init_counters(struct rtl838x_switch_priv *priv)
        struct rtldsa_counter_state *counters;
 
        for (int port = 0; port < priv->r->cpu_port; port++) {
-               if (!priv->ports[port].phy && !priv->ports[port].pcs)
+               if (!priv->ports[port].phy && !priv->ports[port].has_pcs)
                        continue;
 
                counters = &priv->ports[port].counters;
@@ -2593,7 +2619,6 @@ unlock:
 }
 
 const struct phylink_mac_ops rtldsa_83xx_phylink_mac_ops = {
-       .mac_select_pcs         = rtldsa_phylink_mac_select_pcs,
        .mac_config             = rtldsa_83xx_phylink_mac_config,
        .mac_link_down          = rtldsa_83xx_phylink_mac_link_down,
        .mac_link_up            = rtldsa_83xx_phylink_mac_link_up,
@@ -2653,7 +2678,6 @@ const struct dsa_switch_ops rtldsa_83xx_switch_ops = {
 };
 
 const struct phylink_mac_ops rtldsa_93xx_phylink_mac_ops = {
-       .mac_select_pcs         = rtldsa_phylink_mac_select_pcs,
        .mac_config             = rtldsa_93xx_phylink_mac_config,
        .mac_link_down          = rtldsa_93xx_phylink_mac_link_down,
        .mac_link_up            = rtldsa_93xx_phylink_mac_link_up,
index 90efb8540c04b0efd6c3ccb44f595a3c6eb15569..6ed1ed4fc19188ae8c25cd896721323971767d02 100644 (file)
@@ -1003,7 +1003,7 @@ struct rtldsa_port {
        u64 pm;
        u16 pvid;
        bool eee_enabled;
-       struct phylink_pcs *pcs;
+       bool has_pcs;
        int led_set;
        int leds_on_this_port;
        struct rtldsa_counter_state counters;
index 18c83112a70447e4cbca5f0cefdcb2dcb7d8d648..3e406824f57ccf5ce069dbfedb946d59b79a3639 100644 (file)
@@ -2668,7 +2668,7 @@ static void rtl930x_led_init(struct rtl838x_switch_priv *priv)
                sw_w32_mask(0x3 << pos, 0, RTL930X_LED_PORT_FIB_SET_SEL_CTRL(i));
                sw_w32_mask(0x3 << pos, 0, RTL930X_LED_PORT_COPR_SET_SEL_CTRL(i));
 
-               if (!priv->ports[i].phy && !priv->ports[i].pcs && !(forced_leds_per_port[i]))
+               if (!priv->ports[i].phy && !priv->ports[i].has_pcs && !(forced_leds_per_port[i]))
                        continue;
 
                if (forced_leds_per_port[i] > 0)
index 750c121236d6ada64e048fa2366818a55b965260..d10517c27eb782f4b3cce57593a03cb9b869868e 100644 (file)
@@ -1660,7 +1660,7 @@ static void rtldsa_931x_led_init(struct rtl838x_switch_priv *priv)
                sw_w32_mask(0x3 << pos, 0, RTL931X_LED_PORT_COPR_SET_SEL_CTRL(i));
 
                /* Skip port if not present (auto-detect) or not in forced mask */
-               if (!priv->ports[i].phy && !priv->ports[i].pcs && !(forced_leds_per_port[i]))
+               if (!priv->ports[i].phy && !priv->ports[i].has_pcs && !(forced_leds_per_port[i]))
                        continue;
 
                if (forced_leds_per_port[i] > 0)
index 507bb1bbe1a4c5f14303660add47a4321f3fdfbd..cbfeee50b68a2aaf1c1b85d353234b0643a82d01 100644 (file)
@@ -5,11 +5,14 @@
 #include <linux/of.h>
 #include <linux/of_mdio.h>
 #include <linux/of_platform.h>
+#include <linux/pcs/pcs-provider.h>
 #include <linux/phy.h>
 #include <linux/phy/phy-common-props.h>
 #include <linux/phylink.h>
 #include <linux/platform_device.h>
+#include <linux/property.h>
 #include <linux/regmap.h>
+#include <linux/rtnetlink.h>
 
 #define RTPCS_SDS_CNT                          14
 #define RTPCS_MAX_LINKS_PER_SDS                        8
@@ -4241,39 +4244,133 @@ static void rtpcs_sds_put_fwnode(void *data)
        fwnode_handle_put(sds->fwnode);
 }
 
-static void rtpcs_count_links(struct rtpcs_ctrl *ctrl)
+static void rtpcs_del_provider_action(void *data)
 {
-       struct device_node *consumer __free(device_node) = NULL;
-       struct of_phandle_args args;
+       struct rtpcs_serdes *sds = data;
 
-       for_each_node_with_property(consumer, "pcs-handle") {
-               int idx = 0;
+       fwnode_pcs_del_provider(sds->fwnode);
 
-               if (!of_device_is_available(consumer))
+       rtnl_lock();
+       for (int i = 0; i < RTPCS_MAX_LINKS_PER_SDS; i++) {
+               if (!sds->link[i])
                        continue;
 
-               while (!of_parse_phandle_with_args(consumer, "pcs-handle",
-                                                  "#pcs-cells", idx++, &args)) {
-                       struct device_node *arg_np __free(device_node) = args.np;
+               phylink_release_pcs(&sds->link[i]->pcs);
+       }
+       rtnl_unlock();
+}
 
-                       for (int s = 0; s < ctrl->cfg->serdes_count; s++) {
-                               struct rtpcs_serdes *sds = &ctrl->serdes[s];
+static struct rtpcs_serdes *rtpcs_find_serdes(struct rtpcs_ctrl *ctrl,
+                                             struct fwnode_handle *fwnode)
+{
+       for (int i = 0; i < ctrl->cfg->serdes_count; i++) {
+               if (ctrl->serdes[i].fwnode == fwnode)
+                       return &ctrl->serdes[i];
+       }
+       return NULL;
+}
 
-                               if (of_fwnode_handle(arg_np) != sds->fwnode)
-                                       continue;
+/*
+ * Walk the sibling switch's ethernet-ports subtree to learn which MAC port
+ * each (SerDes, link_idx) pair serves. Same "backwards" topology lookup the
+ * sibling MDIO driver does for phy-handle: the DT already encodes the
+ * mapping via per-port pcs-handle properties, so the driver doesn't need a
+ * parallel per-SoC table. pcs_get_state still needs the port number to
+ * index MAC-side link status registers; it reads link_port[] populated
+ * here.
+ */
+static int rtpcs_map_links(struct device *dev, struct rtpcs_ctrl *ctrl)
+{
+       struct fwnode_handle *fw_dev = dev_fwnode(dev);
+       struct fwnode_handle *fw_parent __free(fwnode_handle) = fwnode_get_parent(fw_dev);
+       if (!fw_parent)
+               return -ENODEV;
+
+       struct fwnode_handle *fw_switch __free(fwnode_handle) =
+               fwnode_get_named_child_node(fw_parent, "ethernet-switch");
+       if (!fw_switch)
+               return dev_err_probe(dev, -ENODEV, "%pfwP missing ethernet-switch\n", fw_parent);
+
+       struct fwnode_handle *fw_ports __free(fwnode_handle) =
+               fwnode_get_named_child_node(fw_switch, "ethernet-ports");
+       if (!fw_ports)
+               return dev_err_probe(dev, -ENODEV, "%pfwP missing ethernet-ports\n", fw_switch);
+
+       fwnode_for_each_child_node_scoped(fw_ports, fw_port) {
+               struct fwnode_reference_args args;
+               struct rtpcs_serdes *sds;
+               int link_idx, ret;
+               u32 pn;
+
+               if (fwnode_property_read_u32(fw_port, "reg", &pn))
+                       continue;
 
-                               if (sds->num_of_links >= RTPCS_MAX_LINKS_PER_SDS) {
-                                       dev_warn(ctrl->dev,
-                                                "%pOF: pcs-handle to sds%u exceeds max %u, clamping\n",
-                                                consumer, sds->id, RTPCS_MAX_LINKS_PER_SDS);
-                                       break;
-                               }
+               ret = fwnode_property_get_reference_args(fw_port, "pcs-handle", "#pcs-cells",
+                                                        -1, 0, &args);
+               if (ret)
+                       continue;
 
-                               sds->num_of_links++;
-                               break;
-                       }
-               }
+               struct fwnode_handle *fw_pcs __free(fwnode_handle) = args.fwnode;
+               link_idx = args.args[0];
+
+               if (link_idx >= RTPCS_MAX_LINKS_PER_SDS)
+                       return dev_err_probe(dev, -ERANGE,
+                                            "%pfwP: pcs-handle link %d exceeds max %u\n",
+                                            fw_port, link_idx, RTPCS_MAX_LINKS_PER_SDS);
+
+               sds = rtpcs_find_serdes(ctrl, fw_pcs);
+               if (!sds)
+                       continue;
+
+               if (sds->link_port[link_idx] >= 0)
+                       return dev_err_probe(dev, -EEXIST,
+                                            "%pfwP: sds%u link %d already assigned to port %d\n",
+                                            fw_port, sds->id, link_idx, sds->link_port[link_idx]);
+
+               sds->link_port[link_idx] = pn;
+               sds->num_of_links++;
+       }
+
+       return 0;
+}
+
+static struct phylink_pcs *rtpcs_pcs_get(struct fwnode_reference_args *pcsspec, void *data)
+{
+       struct rtpcs_serdes *sds = data;
+       struct rtpcs_link *link;
+       unsigned int link_idx;
+       struct device *dev;
+
+       dev = sds->ctrl->dev;
+       if (!pcsspec->nargs) {
+               dev_err(dev, "invalid number of cells in 'pcs' property\n");
+               return ERR_PTR(-EINVAL);
        }
+
+       link_idx = pcsspec->args[0];
+       if (link_idx >= RTPCS_MAX_LINKS_PER_SDS)
+               return ERR_PTR(-EINVAL);
+
+       if (sds->link_port[link_idx] < 0) {
+               dev_err(dev, "sds %u link %d not associated with any port\n",
+                       sds->id, link_idx);
+               return ERR_PTR(-ENODEV);
+       }
+
+       if (!sds->link[link_idx]) {
+               link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL);
+               if (!link)
+                       return ERR_PTR(-ENOMEM);
+
+               link->ctrl = sds->ctrl;
+               link->port = sds->link_port[link_idx];
+               link->sds = sds;
+               link->pcs.ops = sds->ctrl->cfg->pcs_ops;
+
+               sds->link[link_idx] = link;
+       }
+
+       return &sds->link[link_idx]->pcs;
 }
 
 static int rtpcs_probe(struct platform_device *pdev)
@@ -4332,7 +4429,9 @@ static int rtpcs_probe(struct platform_device *pdev)
                        return ret;
        }
 
-       rtpcs_count_links(ctrl);
+       ret = rtpcs_map_links(dev, ctrl);
+       if (ret)
+               return ret;
 
        if (ctrl->cfg->init) {
                ret = ctrl->cfg->init(ctrl);
@@ -4346,8 +4445,21 @@ static int rtpcs_probe(struct platform_device *pdev)
         */
        platform_set_drvdata(pdev, ctrl);
 
-       dev_info(dev, "Realtek PCS driver initialized\n");
+       for (i = 0; i < ctrl->cfg->serdes_count; i++) {
+               sds = &ctrl->serdes[i];
+               if (!sds->fwnode)
+                       continue;
 
+               ret = fwnode_pcs_add_provider(sds->fwnode, rtpcs_pcs_get, sds);
+               if (ret)
+                       return ret;
+               ret = devm_add_action_or_reset(dev, rtpcs_del_provider_action,
+                                              sds);
+               if (ret)
+                       return ret;
+       }
+
+       dev_info(dev, "Realtek PCS driver initialized\n");
        return 0;
 }
 
index 30ca2112698d15f8857772f44087b9efa7e472f3..e354b2ee73fc9004eb40d779420786a1ff93a7ed 100644 (file)
@@ -11,13 +11,14 @@ Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
 ---
 --- a/drivers/net/pcs/Kconfig
 +++ b/drivers/net/pcs/Kconfig
-@@ -46,6 +46,15 @@ config PCS_MTK_USXGMII
+@@ -46,6 +46,16 @@ config PCS_MTK_USXGMII
          1000Base-X, 2500Base-X and Cisco SGMII are supported on the same
          differential pairs via an embedded LynxI PCS.
  
 +config PCS_RTL_OTTO
 +      tristate "Realtek Otto SerDes PCS"
 +      depends on MACH_REALTEK_RTL || COMPILE_TEST
++      select FWNODE_PCS
 +      select PHY_COMMON_PROPS
 +      select PHYLINK
 +      select REGMAP