realtek: dsa: rtl930x: handle SFP PHY link state

RTL930x SFP ports may use either a directly attached SerDes module
or a module containing a PHY discovered after switch setup. Static
PHY polling cannot provide link state for the latter, while forcing
every port would override hardware state for PCS and statically
polled PHY links.

Program the MAC link-state source for non-static links. Dynamic SGMII
and out-of-band PHYs select their PHY state source and remain forced.
Dynamic USXGMII PHYs pulse force with the resolved parameters before
returning to PCS-controlled state. Fixed links and CPU ports also
remain forced.

Keep flow-control forcing independent from link-state forcing so
phylink's resolved pause settings remain active when link state returns
to hardware control.

Preserve force mode while a forced link is down so the MAC reports
the down state instead of reverting to its hardware source.

Tested on RTL930x hardware.

Assisted-by: ChatGPT (OpenAI GPT-5.6 Sol)
Link: https://github.com/openwrt/openwrt/pull/25118
Signed-off-by: Jonas Jelonek <jonas@jonasjelonek.de>
This commit is contained in:
Jonas Jelonek committed 2026-10-12 20:06:56 +02:00
1 parent 370eac4f6e
commit 76484bf8b8
4 files changed
+204 -24

No files matched your search

@@ -313,20 +313,60 @@ static void rtldsa_83xx_phylink_mac_config(struct phylink_config *config,
regmap_write(priv->map, priv->r->mac_force_mode_ctrl(port), mcr);
}
static void
rtldsa_mac_link_state_source_set(struct rtl838x_switch_priv *priv, int port,
enum rtldsa_mac_link_state_source source,
phy_interface_t interface)
{
int err;
if (!priv->r->mac_link_state_source_set)
return;
err = priv->r->mac_link_state_source_set(priv, port, source, interface);
if (err)
dev_err(priv->dev,
"failed to set MAC link-state source for port %d: %d\n",
port, err);
}
static void rtldsa_93xx_phylink_mac_config(struct phylink_config *config,
unsigned int mode,
const struct phylink_link_state *state)
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct rtl838x_switch_priv *priv = dp->ds->priv;
bool force_dynamic_phy;
int port = dp->index;
/* Nothing to be done for the CPU-port */
if (port == priv->r->cpu_port)
return;
/* Disable MAC completely */
regmap_write(priv->map, priv->r->mac_force_mode_ctrl(port), 0);
force_dynamic_phy = priv->family_id == RTL9300_FAMILY_ID &&
!priv->ports[port].phy &&
(mode == MLO_AN_PHY || state->interface == PHY_INTERFACE_MODE_SGMII);
/* Select PCS before dropping force mode to avoid a transient link down. */
if (!priv->ports[port].phy && phylink_autoneg_inband(mode) &&
!force_dynamic_phy)
rtldsa_mac_link_state_source_set(priv, port,
RTLDSA_MAC_LINK_STATE_SOURCE_PCS,
state->interface);
if (priv->family_id == RTL9300_FAMILY_ID &&
(mode == MLO_AN_FIXED || force_dynamic_phy)) {
/* Keep links that require force mode forced down. */
regmap_write(priv->map, priv->r->mac_force_mode_ctrl(port),
RTL930X_FORCE_EN | RTL930X_MAC_FORCE_FC_EN);
if (force_dynamic_phy)
rtldsa_mac_link_state_source_set(priv, port,
RTLDSA_MAC_LINK_STATE_SOURCE_PHY,
state->interface);
} else {
/* Disable MAC completely */
regmap_write(priv->map, priv->r->mac_force_mode_ctrl(port), 0);
}
}
static void rtldsa_phylink_mac_link_down(struct phylink_config *config,
@@ -335,14 +375,42 @@ static void rtldsa_phylink_mac_link_down(struct phylink_config *config,
{
struct dsa_port *dp = dsa_phylink_to_port(config);
struct rtl838x_switch_priv *priv = dp->ds->priv;
bool force_dynamic_phy;
int port = dp->index;
u32 mask;
force_dynamic_phy = priv->family_id == RTL9300_FAMILY_ID &&
!priv->ports[port].phy &&
(mode == MLO_AN_PHY || interface == PHY_INTERFACE_MODE_SGMII);
/* Stop TX/RX to port */
regmap_update_bits(priv->map, priv->r->mac_port_ctrl(port), 0x3, 0);
/* No longer force link */
regmap_update_bits(priv->map, priv->r->mac_force_mode_ctrl(port),
priv->r->mac_force_mode_mask, 0);
if (!priv->ports[port].phy && phylink_autoneg_inband(mode) &&
!force_dynamic_phy)
rtldsa_mac_link_state_source_set(priv, port,
RTLDSA_MAC_LINK_STATE_SOURCE_PCS,
interface);
if (priv->family_id == RTL9300_FAMILY_ID &&
(dsa_port_is_cpu(dp) || mode == MLO_AN_FIXED || force_dynamic_phy)) {
/* Preserve force mode while forcing the link down. */
regmap_update_bits(priv->map, priv->r->mac_force_mode_ctrl(port),
RTL930X_FORCE_LINK_EN, 0);
if (force_dynamic_phy)
rtldsa_mac_link_state_source_set(priv, port,
RTLDSA_MAC_LINK_STATE_SOURCE_PHY,
interface);
} else {
/* No longer force link */
mask = priv->r->mac_force_mode_mask;
if (priv->family_id == RTL9300_FAMILY_ID)
mask |= RTL930X_MAC_FORCE_FC_EN;
regmap_update_bits(priv->map, priv->r->mac_force_mode_ctrl(port),
mask, 0);
}
}
static void rtldsa_83xx_phylink_mac_link_up(struct phylink_config *config,
@@ -419,7 +487,67 @@ static void rtldsa_93xx_phylink_mac_link_up(struct phylink_config *config,
struct dsa_port *dp = dsa_phylink_to_port(config);
struct rtl838x_switch_priv *priv = dp->ds->priv;
int port = dp->index;
u32 mcr, spdsel;
bool dynamic_phy, force_dynamic_phy, keep_forced;
u32 force_en, mcr, spdsel;
if (priv->family_id != RTL9300_FAMILY_ID)
goto restart;
/*
* The RTL93xx polling machinery does more than issue MDIO
* transactions. It collects resolved link state and distributes it
* internally to the MAC, so polling must remain enabled for
* SerDes-backed SFP ports even when there is no PHY to poll over MDIO.
*
* The MDIO driver owns the polling setup. It enables polling for PHY
* and SerDes ports and initializes MAC_TYPE/PHY_ABLTY from the static,
* firmware-described topology. That topology cannot describe a PHY
* which appears later in a hot-plugged SFP module. Only DSA learns
* about such a PHY through phylink, so the source selector is
* deliberately shared: MDIO initializes it, while DSA updates it as
* the active SFP link-state source changes. DSA does not disable
* polling here; it only selects whether the polling machinery supplies
* the MAC with state from the PCS or from the dynamic PHY.
*
* Accordingly, direct links use PCS state, while statically described
* PHYs retain the MDIO driver's hardware-polling selection. Dynamic
* SGMII and out-of-band PHYs select PHY state and remain forced.
* Dynamic USXGMII PHYs use PCS state after a force pulse refreshes the
* MAC parameters; fixed links and CPU ports also remain forced.
*/
dynamic_phy = !priv->ports[port].phy && phydev;
force_dynamic_phy = dynamic_phy &&
(!phylink_autoneg_inband(mode) ||
interface == PHY_INTERFACE_MODE_SGMII);
keep_forced = dsa_port_is_cpu(dp) || mode == MLO_AN_FIXED ||
force_dynamic_phy;
force_en = RTL930X_FORCE_EN;
if (dynamic_phy &&
(interface == PHY_INTERFACE_MODE_SGMII ||
interface == PHY_INTERFACE_MODE_USXGMII)) {
if (phydev->speed != SPEED_UNKNOWN)
speed = phydev->speed;
if (phydev->duplex != DUPLEX_UNKNOWN)
duplex = phydev->duplex;
}
regmap_read(priv->map, priv->r->mac_force_mode_ctrl(port), &mcr);
mcr &= ~(RTL930X_RX_PAUSE_EN | RTL930X_TX_PAUSE_EN);
mcr |= RTL930X_MAC_FORCE_FC_EN;
if (tx_pause)
mcr |= RTL930X_TX_PAUSE_EN;
if (rx_pause)
mcr |= RTL930X_RX_PAUSE_EN;
if (!keep_forced && !dynamic_phy) {
if (!priv->ports[port].phy)
rtldsa_mac_link_state_source_set(priv, port,
RTLDSA_MAC_LINK_STATE_SOURCE_PCS,
interface);
mcr &= ~force_en;
regmap_write(priv->map, priv->r->mac_force_mode_ctrl(port), mcr);
goto restart;
}
if (speed == SPEED_10000)
spdsel = RTL_SPEED_10000;
@@ -434,30 +562,32 @@ static void rtldsa_93xx_phylink_mac_link_up(struct phylink_config *config,
else
spdsel = RTL_SPEED_10;
regmap_read(priv->map, priv->r->mac_force_mode_ctrl(port), &mcr);
mcr &= ~RTL930X_DUPLEX_MODE;
mcr &= ~RTL930X_SPEED_MASK;
mcr |= force_en | RTL930X_MAC_FORCE_FC_EN | RTL930X_FORCE_LINK_EN;
mcr |= spdsel << RTL930X_SPEED_SHIFT;
if (dynamic_phy)
mcr &= ~RTL930X_MEDIA_SEL;
if (priv->family_id == RTL9300_FAMILY_ID) {
mcr &= ~RTL930X_RX_PAUSE_EN;
mcr &= ~RTL930X_TX_PAUSE_EN;
mcr &= ~RTL930X_DUPLEX_MODE;
mcr &= ~RTL930X_SPEED_MASK;
mcr |= RTL930X_FORCE_LINK_EN;
mcr |= spdsel << RTL930X_SPEED_SHIFT;
if (tx_pause)
mcr |= RTL930X_TX_PAUSE_EN;
if (rx_pause)
mcr |= RTL930X_RX_PAUSE_EN;
if (duplex == DUPLEX_FULL || priv->lagmembers & BIT_ULL(port))
mcr |= RTL930X_DUPLEX_MODE;
if (dsa_port_is_cpu(dp) || priv->ports[port].phy)
mcr |= RTL930X_FORCE_EN;
}
if (duplex == DUPLEX_FULL || priv->lagmembers & BIT_ULL(port))
mcr |= RTL930X_DUPLEX_MODE;
pr_debug("%s port %d, mode %x, speed %d, duplex %d, txpause %d, rxpause %d: set mcr=%08x\n",
__func__, port, mode, speed, duplex, tx_pause, rx_pause, mcr);
regmap_write(priv->map, priv->r->mac_force_mode_ctrl(port), mcr);
/* Pulse state force enables so hardware latches the new parameters. */
regmap_write(priv->map, priv->r->mac_force_mode_ctrl(port),
mcr & ~force_en);
if (keep_forced)
regmap_write(priv->map, priv->r->mac_force_mode_ctrl(port), mcr);
/* Keep force mode active while switching hardware link sources. */
if (force_dynamic_phy)
rtldsa_mac_link_state_source_set(priv, port,
RTLDSA_MAC_LINK_STATE_SOURCE_PHY,
interface);
restart:
/* Restart TX/RX to port */
regmap_update_bits(priv->map, priv->r->mac_port_ctrl(port), 0x3, 0x3);
}
@@ -13,5 +13,12 @@
#define RTL930X_FORCE_EN BIT(0)
#define RTL930X_FORCE_LINK_EN BIT(1)
#define RTL930X_MAC_FORCE_FC_EN BIT(9)
#define RTL930X_MEDIA_SEL BIT(16)
enum rtldsa_mac_link_state_source {
RTLDSA_MAC_LINK_STATE_SOURCE_PCS,
RTLDSA_MAC_LINK_STATE_SOURCE_PHY,
};
#endif /* _OTTO_MAC_H */
@@ -11,6 +11,7 @@
#include "l2.h"
#include "l3_limits.h"
#include "lag.h"
#include "mac.h"
#include "mirror.h"
#include "pie.h"
#include "stats.h"
@@ -397,6 +398,10 @@ struct rtldsa_config {
u32 mac_force_mode_mask;
int (*mac_force_mode_ctrl)(int port);
int (*mac_port_ctrl)(int port);
int (*mac_link_state_source_set)(struct rtl838x_switch_priv *priv,
int port,
enum rtldsa_mac_link_state_source source,
phy_interface_t interface);
/**
* @mac_capabilities: supported MAC capabilities
@@ -3,6 +3,7 @@
#include <asm/mach-rtl-otto/mach-rtl-otto.h>
#include <linux/etherdevice.h>
#include <linux/inetdevice.h>
#include <linux/regmap.h>
#include "lag.h"
#include "l2.h"
@@ -90,6 +91,13 @@
/* get shift for given led in any set */
#define RTL930X_LED_SET_LEDX_SHIFT(x) (16 * (x % 2))
#define RTL930X_SMI_MAC_TYPE_CTRL 0xca04
#define RTL930X_SMI_MAC_TYPE_P0_23(pn) (GENMASK(1, 0) << (((pn) / 4) * 2))
#define RTL930X_SMI_MAC_TYPE_P24_27(pn) (GENMASK(2, 0) << (((pn) - 24) * 3 + 12))
#define RTL930X_SMI_MAC_TYPE_PCS 0x0
#define RTL930X_SMI_MAC_TYPE_MULTIGIG_PHY 0x1
#define RTL930X_SMI_MAC_TYPE_1G_PHY 0x3
void rtldsa_930x_print_matrix(void)
{
int tbl = otto_table_acquire(RTL9300_TBL_PORT_ISO_CTRL);
@@ -543,6 +551,35 @@ static void rtl930x_led_init(struct rtl838x_switch_priv *priv)
dev_dbg(dev, "%08x: %08x\n", 0xbb00cc00 + i * 4, sw_r32(0xcc00 + i * 4));
}
static int
rtldsa_930x_mac_link_state_source_set(struct rtl838x_switch_priv *priv,
int port,
enum rtldsa_mac_link_state_source source,
phy_interface_t interface)
{
u32 mask, src_val;
if (port < 24)
mask = RTL930X_SMI_MAC_TYPE_P0_23(port);
else if (port < 28)
mask = RTL930X_SMI_MAC_TYPE_P24_27(port);
else
return 0;
if (source == RTLDSA_MAC_LINK_STATE_SOURCE_PCS)
src_val = RTL930X_SMI_MAC_TYPE_PCS;
else if (interface == PHY_INTERFACE_MODE_2500BASEX ||
interface == PHY_INTERFACE_MODE_10GBASER ||
interface == PHY_INTERFACE_MODE_USXGMII ||
interface == PHY_INTERFACE_MODE_10G_QXGMII)
src_val = RTL930X_SMI_MAC_TYPE_MULTIGIG_PHY;
else
src_val = RTL930X_SMI_MAC_TYPE_1G_PHY;
return regmap_update_bits(priv->map, RTL930X_SMI_MAC_TYPE_CTRL,
mask, field_prep(mask, src_val));
}
const struct rtldsa_config rtldsa_930x_cfg = {
.switch_ops = &rtldsa_93xx_switch_ops,
.phylink_mac_ops = &rtldsa_93xx_phylink_mac_ops,
@@ -599,6 +636,7 @@ const struct rtldsa_config rtldsa_930x_cfg = {
.mac_link_sts = RTL930X_MAC_LINK_STS,
.mac_force_mode_mask = RTL930X_FORCE_EN | RTL930X_FORCE_LINK_EN,
.mac_force_mode_ctrl = rtl930x_mac_force_mode_ctrl,
.mac_link_state_source_set = rtldsa_930x_mac_link_state_source_set,
.mac_port_ctrl = rtl930x_mac_port_ctrl,
.mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 | MAC_100 |
MAC_1000FD | MAC_2500FD | MAC_5000FD | MAC_10000FD,