From: Jonas Jelonek Date: Mon, 25 May 2026 21:58:14 +0000 (+0000) Subject: realtek: migrate PCS to fwnode_pcs provider/consumer API X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=a4965dbf481024582b4fd62705886d2c554ddd0d;p=thirdparty%2Fopenwrt.git realtek: migrate PCS to fwnode_pcs provider/consumer API 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) Link: https://github.com/openwrt/openwrt/pull/23539 Signed-off-by: Jonas Jelonek --- diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c index 45f645065b9..a85bf1087fd 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c @@ -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)) diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c index 7b28c6f4e84..194c55a891b 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/dsa.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #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, diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h index 90efb8540c0..6ed1ed4fc19 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl-otto.h @@ -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; diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl930x.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl930x.c index 18c83112a70..3e406824f57 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl930x.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl930x.c @@ -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) diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl931x.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl931x.c index 750c121236d..d10517c27eb 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl931x.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/rtl931x.c @@ -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) diff --git a/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c b/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c index 507bb1bbe1a..cbfeee50b68 100644 --- a/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c +++ b/target/linux/realtek/files-6.18/drivers/net/pcs/pcs-rtl-otto.c @@ -5,11 +5,14 @@ #include #include #include +#include #include #include #include #include +#include #include +#include #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; } diff --git a/target/linux/realtek/patches-6.18/730-add-pcs-rtl-otto.patch b/target/linux/realtek/patches-6.18/730-add-pcs-rtl-otto.patch index 30ca2112698..e354b2ee73f 100644 --- a/target/linux/realtek/patches-6.18/730-add-pcs-rtl-otto.patch +++ b/target/linux/realtek/patches-6.18/730-add-pcs-rtl-otto.patch @@ -11,13 +11,14 @@ Signed-off-by: Markus Stockhausen --- --- 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