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>
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 */
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))
#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"
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);
}
* 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));
}
* 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));
}
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
__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
__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,
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);
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;
}
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,
};
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,
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;
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)
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)
#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
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)
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);
*/
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;
}
---
--- 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