* [PATCH net-next 1/5] net: dsa: mxl862xx: store firmware version for feature gating
2026-05-19 17:38 [PATCH net-next 0/5] net: dsa: mxl862xx: SerDes ports and stats Daniel Golle
@ 2026-05-19 17:38 ` Daniel Golle
2026-05-19 17:38 ` [PATCH net-next 2/5] net: dsa: mxl862xx: move phylink stubs to mxl862xx-phylink.c Daniel Golle
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Daniel Golle @ 2026-05-19 17:38 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King,
linux-kernel, netdev
Query the firmware version at init (already done in wait_ready),
cache it in priv->fw_version, and provide MXL862XX_FW_VER_MIN()
for version-gated code paths throughout the driver.
The union mxl862xx_fw_version lays out major/minor/revision so
that the u32 raw field compares with natural version ordering on
both big- and little-endian machines.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 3 +++
drivers/net/dsa/mxl862xx/mxl862xx.h | 36 +++++++++++++++++++++++++++++
2 files changed, 39 insertions(+)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index b60482d93a85..2f22adedfbf6 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -257,6 +257,9 @@ static int mxl862xx_wait_ready(struct dsa_switch *ds)
ver.iv_major, ver.iv_minor,
le16_to_cpu(ver.iv_revision),
le32_to_cpu(ver.iv_build_num));
+ priv->fw_version.major = ver.iv_major;
+ priv->fw_version.minor = ver.iv_minor;
+ priv->fw_version.revision = le16_to_cpu(ver.iv_revision);
return 0;
not_ready_yet:
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 80053ab40e4c..79fd32c4db4e 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -3,6 +3,7 @@
#ifndef __MXL862XX_H
#define __MXL862XX_H
+#include <asm/byteorder.h>
#include <linux/mdio.h>
#include <linux/workqueue.h>
#include <net/dsa.h>
@@ -241,6 +242,38 @@ struct mxl862xx_port {
spinlock_t stats_lock; /* protects stats accumulators */
};
+/**
+ * union mxl862xx_fw_version - firmware version for comparison and display
+ * @major: firmware major version
+ * @minor: firmware minor version
+ * @revision: firmware revision number
+ * @raw: combined u32 for direct >= comparison (major most significant)
+ *
+ * The struct layout places major in the most-significant byte of the
+ * u32 on both big- and little-endian machines, so raw values compare
+ * with the natural major > minor > revision ordering.
+ */
+union mxl862xx_fw_version {
+ struct {
+#if defined(__BIG_ENDIAN)
+ u8 major;
+ u8 minor;
+ u16 revision;
+#elif defined(__LITTLE_ENDIAN)
+ u16 revision;
+ u8 minor;
+ u8 major;
+#endif
+ };
+ u32 raw;
+};
+
+#define MXL862XX_FW_VER(maj, min, rev) \
+ ((union mxl862xx_fw_version){ .major = (maj), .minor = (min), \
+ .revision = (rev) }).raw
+#define MXL862XX_FW_VER_MIN(priv, maj, min, rev) \
+ ((priv)->fw_version.raw >= MXL862XX_FW_VER(maj, min, rev))
+
/* Bit indices for struct mxl862xx_priv::flags */
#define MXL862XX_FLAG_CRC_ERR 0
#define MXL862XX_FLAG_WORK_STOPPED 1
@@ -258,6 +291,8 @@ struct mxl862xx_port {
* @drop_meter: index of the single shared zero-rate firmware meter
* used to unconditionally drop traffic (used to block
* flooding)
+ * @fw_version: cached firmware version, populated at probe and
+ * compared with MXL862XX_FW_VER_MIN()
* @ports: per-port state, indexed by switch port number
* @bridges: maps DSA bridge number to firmware bridge ID;
* zero means no firmware bridge allocated for that
@@ -275,6 +310,7 @@ struct mxl862xx_priv {
struct work_struct crc_err_work;
unsigned long flags;
u16 drop_meter;
+ union mxl862xx_fw_version fw_version;
struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
u16 bridges[MXL862XX_MAX_BRIDGES + 1];
u16 evlan_ingress_size;
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net-next 2/5] net: dsa: mxl862xx: move phylink stubs to mxl862xx-phylink.c
2026-05-19 17:38 [PATCH net-next 0/5] net: dsa: mxl862xx: SerDes ports and stats Daniel Golle
2026-05-19 17:38 ` [PATCH net-next 1/5] net: dsa: mxl862xx: store firmware version for feature gating Daniel Golle
@ 2026-05-19 17:38 ` Daniel Golle
2026-05-19 17:38 ` [PATCH net-next 3/5] net: dsa: mxl862xx: move API macros to mxl862xx-host.h Daniel Golle
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Daniel Golle @ 2026-05-19 17:38 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King,
linux-kernel, netdev
Move the phylink MAC operations and get_caps callback from mxl862xx.c
into a dedicated mxl862xx-phylink.c file. This prepares for the SerDes
PCS implementation which adds substantial phylink/PCS code -- keeping
it in a separate file avoids function-position churn in the main
driver file.
No functional change.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/Makefile | 2 +-
drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 51 +++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-phylink.h | 14 ++++++
drivers/net/dsa/mxl862xx/mxl862xx.c | 38 +--------------
4 files changed, 67 insertions(+), 38 deletions(-)
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
create mode 100644 drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
diff --git a/drivers/net/dsa/mxl862xx/Makefile b/drivers/net/dsa/mxl862xx/Makefile
index d23dd3cd511d..a7be0e6669df 100644
--- a/drivers/net/dsa/mxl862xx/Makefile
+++ b/drivers/net/dsa/mxl862xx/Makefile
@@ -1,3 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_NET_DSA_MXL862) += mxl862xx_dsa.o
-mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o
+mxl862xx_dsa-y := mxl862xx.o mxl862xx-host.o mxl862xx-phylink.o
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
new file mode 100644
index 000000000000..f17c429d1f1d
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Phylink and PCS support for MaxLinear MxL862xx switch family
+ *
+ * Copyright (C) 2024 MaxLinear Inc.
+ * Copyright (C) 2025 John Crispin <john@phrozen.org>
+ * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
+ */
+
+#include <linux/phylink.h>
+#include <net/dsa.h>
+
+#include "mxl862xx.h"
+#include "mxl862xx-phylink.h"
+
+void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
+ MAC_100 | MAC_1000 | MAC_2500FD;
+
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+}
+
+static void mxl862xx_phylink_mac_config(struct phylink_config *config,
+ unsigned int mode,
+ const struct phylink_link_state *state)
+{
+}
+
+static void mxl862xx_phylink_mac_link_down(struct phylink_config *config,
+ unsigned int mode,
+ phy_interface_t interface)
+{
+}
+
+static void mxl862xx_phylink_mac_link_up(struct phylink_config *config,
+ struct phy_device *phydev,
+ unsigned int mode,
+ phy_interface_t interface,
+ int speed, int duplex,
+ bool tx_pause, bool rx_pause)
+{
+}
+
+const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
+ .mac_config = mxl862xx_phylink_mac_config,
+ .mac_link_down = mxl862xx_phylink_mac_link_down,
+ .mac_link_up = mxl862xx_phylink_mac_link_up,
+};
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
new file mode 100644
index 000000000000..c3d5215bdf60
--- /dev/null
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __MXL862XX_PHYLINK_H
+#define __MXL862XX_PHYLINK_H
+
+#include <linux/phylink.h>
+
+#include "mxl862xx.h"
+
+extern const struct phylink_mac_ops mxl862xx_phylink_mac_ops;
+void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config);
+
+#endif /* __MXL862XX_PHYLINK_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 2f22adedfbf6..a193f3c07d35 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -22,6 +22,7 @@
#include "mxl862xx-api.h"
#include "mxl862xx-cmd.h"
#include "mxl862xx-host.h"
+#include "mxl862xx-phylink.h"
#define MXL862XX_API_WRITE(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
@@ -1424,16 +1425,6 @@ static void mxl862xx_port_teardown(struct dsa_switch *ds, int port)
priv->ports[port].setup_done = false;
}
-static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
- struct phylink_config *config)
-{
- config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
- MAC_100 | MAC_1000 | MAC_2500FD;
-
- __set_bit(PHY_INTERFACE_MODE_INTERNAL,
- config->supported_interfaces);
-}
-
static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db)
{
struct mxl862xx_priv *priv = ds->priv;
@@ -2099,33 +2090,6 @@ static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_stats64 = mxl862xx_get_stats64,
};
-static void mxl862xx_phylink_mac_config(struct phylink_config *config,
- unsigned int mode,
- const struct phylink_link_state *state)
-{
-}
-
-static void mxl862xx_phylink_mac_link_down(struct phylink_config *config,
- unsigned int mode,
- phy_interface_t interface)
-{
-}
-
-static void mxl862xx_phylink_mac_link_up(struct phylink_config *config,
- struct phy_device *phydev,
- unsigned int mode,
- phy_interface_t interface,
- int speed, int duplex,
- bool tx_pause, bool rx_pause)
-{
-}
-
-static const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
- .mac_config = mxl862xx_phylink_mac_config,
- .mac_link_down = mxl862xx_phylink_mac_link_down,
- .mac_link_up = mxl862xx_phylink_mac_link_up,
-};
-
static int mxl862xx_probe(struct mdio_device *mdiodev)
{
struct device *dev = &mdiodev->dev;
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net-next 3/5] net: dsa: mxl862xx: move API macros to mxl862xx-host.h
2026-05-19 17:38 [PATCH net-next 0/5] net: dsa: mxl862xx: SerDes ports and stats Daniel Golle
2026-05-19 17:38 ` [PATCH net-next 1/5] net: dsa: mxl862xx: store firmware version for feature gating Daniel Golle
2026-05-19 17:38 ` [PATCH net-next 2/5] net: dsa: mxl862xx: move phylink stubs to mxl862xx-phylink.c Daniel Golle
@ 2026-05-19 17:38 ` Daniel Golle
2026-05-19 17:39 ` [PATCH net-next 4/5] net: dsa: mxl862xx: add support for SerDes ports Daniel Golle
2026-05-19 17:39 ` [PATCH net-next 5/5] net: dsa: mxl862xx: add SerDes ethtool statistics Daniel Golle
4 siblings, 0 replies; 8+ messages in thread
From: Daniel Golle @ 2026-05-19 17:38 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King,
linux-kernel, netdev
Move the MXL862XX_API_WRITE, MXL862XX_API_READ and
MXL862XX_API_READ_QUIET convenience macros from mxl862xx.c to
mxl862xx-host.h next to the mxl862xx_api_wrap() prototype they wrap.
This makes them available to other compilation units that include
mxl862xx-host.h, which is needed once the SerDes PCS code in
mxl862xx-phylink.c also calls firmware commands.
No functional change.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-host.h | 8 ++++++++
drivers/net/dsa/mxl862xx/mxl862xx.c | 7 -------
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-host.h b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
index 84512a30bc18..66d6ae198aff 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.h
@@ -9,6 +9,14 @@ void mxl862xx_host_init(struct mxl862xx_priv *priv);
void mxl862xx_host_shutdown(struct mxl862xx_priv *priv);
int mxl862xx_api_wrap(struct mxl862xx_priv *priv, u16 cmd, void *data, u16 size,
bool read, bool quiet);
+
+#define MXL862XX_API_WRITE(dev, cmd, data) \
+ mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
+#define MXL862XX_API_READ(dev, cmd, data) \
+ mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false)
+#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
+ mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
+
int mxl862xx_reset(struct mxl862xx_priv *priv);
#endif /* __MXL862XX_HOST_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index a193f3c07d35..0b1a23364eb5 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -24,13 +24,6 @@
#include "mxl862xx-host.h"
#include "mxl862xx-phylink.h"
-#define MXL862XX_API_WRITE(dev, cmd, data) \
- mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
-#define MXL862XX_API_READ(dev, cmd, data) \
- mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, false)
-#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
- mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
-
/* Polling interval for RMON counter accumulation. At 2.5 Gbps with
* minimum-size (64-byte) frames, a 32-bit packet counter wraps in ~880s.
* 2s gives a comfortable margin.
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 4/5] net: dsa: mxl862xx: add support for SerDes ports
2026-05-19 17:38 [PATCH net-next 0/5] net: dsa: mxl862xx: SerDes ports and stats Daniel Golle
` (2 preceding siblings ...)
2026-05-19 17:38 ` [PATCH net-next 3/5] net: dsa: mxl862xx: move API macros to mxl862xx-host.h Daniel Golle
@ 2026-05-19 17:39 ` Daniel Golle
2026-05-19 17:39 ` [PATCH net-next 5/5] net: dsa: mxl862xx: add SerDes ethtool statistics Daniel Golle
4 siblings, 0 replies; 8+ messages in thread
From: Daniel Golle @ 2026-05-19 17:39 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King,
linux-kernel, netdev
The MxL862xx has two XPCS/SerDes interfaces (XPCS0 for ports 9-12,
XPCS1 for ports 13-16). Each can operate in various single-lane modes
(SGMII, 1000BASE-X, 2500BASE-X, 10GBASE-R, 10GBASE-KR, USXGMII) or as
QSGMII or 10G_QXGMII providing four sub-ports per interface.
Implement phylink PCS operations using the firmware's XPCS API:
- pcs_config: configure negotiation mode and CL37/SGMII advertising.
- pcs_get_state: read link/speed/duplex/LPA from firmware and decode
using phylink's standard CL37, SGMII, and USXGMII decoders, with
firmware-resolved speed/duplex override for downshift detection.
- pcs_an_restart: restart CL37 or CL73 auto-negotiation.
- pcs_link_up: force speed/duplex for SGMII.
- pcs_inband_caps: report per-mode in-band status capabilities.
Register a PCS instance for each SerDes interface and
QSGMII/10G_QXGMII sub-ports during setup. Advertise the supported
interface modes in phylink_get_caps based on port number.
Lacking support for expressing PHY-side role modes in Linux only the
MAC-side of SGMII, QSGMII, USXGMII and 10G_QXGMII are implemented for
now.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 302 +++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 9 +
drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 306 +++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx-phylink.h | 10 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 6 +-
drivers/net/dsa/mxl862xx/mxl862xx.h | 22 ++
6 files changed, 652 insertions(+), 3 deletions(-)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
index fb21ddc1bf1c..6e332c99b245 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -1366,4 +1366,306 @@ struct mxl862xx_rmon_port_cnt {
__le64 tx_good_bytes;
} __packed;
+/**
+ * enum mxl862xx_xpcs_if_mode - XPCS interface mode
+ * @MXL862XX_XPCS_IF_SGMII: SGMII
+ * @MXL862XX_XPCS_IF_1000BASEX: 1000BASE-X
+ * @MXL862XX_XPCS_IF_2500BASEX: 2500BASE-X
+ * @MXL862XX_XPCS_IF_USXGMII: USXGMII (single or quad)
+ * @MXL862XX_XPCS_IF_10GBASER: 10GBASE-R
+ * @MXL862XX_XPCS_IF_10GKR: 10GBASE-KR
+ * @MXL862XX_XPCS_IF_5GBASER: 5GBASE-R
+ * @MXL862XX_XPCS_IF_QSGMII: QSGMII
+ */
+enum mxl862xx_xpcs_if_mode {
+ MXL862XX_XPCS_IF_SGMII = 0,
+ MXL862XX_XPCS_IF_1000BASEX = 1,
+ MXL862XX_XPCS_IF_2500BASEX = 2,
+ MXL862XX_XPCS_IF_USXGMII = 3,
+ MXL862XX_XPCS_IF_10GBASER = 4,
+ MXL862XX_XPCS_IF_10GKR = 5,
+ MXL862XX_XPCS_IF_5GBASER = 6,
+ MXL862XX_XPCS_IF_QSGMII = 7,
+};
+
+/**
+ * enum mxl862xx_xpcs_neg_mode - PCS negotiation mode
+ * @MXL862XX_XPCS_NEG_NONE: no inband negotiation
+ * @MXL862XX_XPCS_NEG_INBAND_AN_OFF: inband selected but AN disabled
+ * @MXL862XX_XPCS_NEG_INBAND_AN_ON: inband with AN enabled
+ */
+enum mxl862xx_xpcs_neg_mode {
+ MXL862XX_XPCS_NEG_NONE = 0,
+ MXL862XX_XPCS_NEG_INBAND_AN_OFF = 1,
+ MXL862XX_XPCS_NEG_INBAND_AN_ON = 2,
+};
+
+/**
+ * enum mxl862xx_xpcs_role - PCS protocol role
+ * @MXL862XX_XPCS_ROLE_MAC: local end is MAC side (TX_CONFIG = 0)
+ * @MXL862XX_XPCS_ROLE_PHY: local end is PHY side (TX_CONFIG = 1)
+ *
+ * Selects the role the XPCS plays in protocols that have an asymmetric
+ * AN code word (Cisco SGMII / QSGMII / USXGMII). Drives
+ * VR_MII_AN_CTRL.TX_CONFIG: 0 means the local end receives the partner's
+ * AN word, 1 means it sources one. Ignored for symmetric protocols
+ * (1000BASE-X, 2500BASE-X, 10GBASE-R/KR).
+ */
+enum mxl862xx_xpcs_role {
+ MXL862XX_XPCS_ROLE_MAC = 0,
+ MXL862XX_XPCS_ROLE_PHY = 1,
+};
+
+/**
+ * enum mxl862xx_xpcs_usx_lane_mode - USXGMII lane mode
+ * @MXL862XX_XPCS_USX_SINGLE: single USXGMII lane
+ * @MXL862XX_XPCS_USX_QUAD: quad USXGMII (4 ports per lane)
+ */
+enum mxl862xx_xpcs_usx_lane_mode {
+ MXL862XX_XPCS_USX_SINGLE = 0,
+ MXL862XX_XPCS_USX_QUAD = 1,
+};
+
+/**
+ * union mxl862xx_xpcs_an_word - XPCS AN code word, tagged by interface mode
+ * @cl37: 16-bit base-page word exchanged over the CL37 hardware AN path
+ * (SR_MII_AN_ADV on write, SR_MII_LP_BABL on read). Carries the
+ * 802.3 CL37 base page for 1000BASE-X/2500BASE-X and the Cisco
+ * SGMII config word for SGMII/QSGMII.
+ * @usx: USXGMII 16-bit AN code word, MDIO_USXGMII_* layout
+ * @cl73: CL73 48-bit base page (10GBASE-KR), three 16-bit registers per
+ * 802.3 Annex 28C
+ * @cl73.adv1: CL73 SR_AN_ADV1 / SR_AN_LP_ABL1
+ * @cl73.adv2: CL73 SR_AN_ADV2 / SR_AN_LP_ABL2
+ * @cl73.adv3: CL73 SR_AN_ADV3 / SR_AN_LP_ABL3
+ *
+ * The host picks the right member based on the interface field of the
+ * surrounding struct (and, for the asymmetric protocols, on the role).
+ */
+union mxl862xx_xpcs_an_word {
+ __le16 cl37;
+ __le16 usx;
+ struct {
+ __le16 adv1;
+ __le16 adv2;
+ __le16 adv3;
+ } cl73;
+} __packed;
+
+/**
+ * enum mxl862xx_xpcs_speed - PCS speed values
+ * @MXL862XX_XPCS_SPEED_UNKNOWN: unknown speed
+ * @MXL862XX_XPCS_SPEED_10: 10 Mbps
+ * @MXL862XX_XPCS_SPEED_100: 100 Mbps
+ * @MXL862XX_XPCS_SPEED_1000: 1000 Mbps
+ * @MXL862XX_XPCS_SPEED_2500: 2500 Mbps
+ * @MXL862XX_XPCS_SPEED_5000: 5000 Mbps
+ * @MXL862XX_XPCS_SPEED_10000: 10000 Mbps
+ */
+enum mxl862xx_xpcs_speed {
+ MXL862XX_XPCS_SPEED_UNKNOWN = 0,
+ MXL862XX_XPCS_SPEED_10 = 10,
+ MXL862XX_XPCS_SPEED_100 = 100,
+ MXL862XX_XPCS_SPEED_1000 = 1000,
+ MXL862XX_XPCS_SPEED_2500 = 2500,
+ MXL862XX_XPCS_SPEED_5000 = 5000,
+ MXL862XX_XPCS_SPEED_10000 = 10000,
+};
+
+/**
+ * enum mxl862xx_xpcs_duplex - PCS duplex mode
+ * @MXL862XX_XPCS_DUPLEX_HALF: half duplex
+ * @MXL862XX_XPCS_DUPLEX_FULL: full duplex
+ */
+enum mxl862xx_xpcs_duplex {
+ MXL862XX_XPCS_DUPLEX_HALF = 0,
+ MXL862XX_XPCS_DUPLEX_FULL = 1,
+};
+
+/**
+ * enum mxl862xx_xpcs_loopback_mode - XPCS loopback mode
+ * @MXL862XX_XPCS_LB_DISABLE: disable all loopback
+ * @MXL862XX_XPCS_LB_PCS_SERIAL: PCS TX-to-RX serial loopback
+ * @MXL862XX_XPCS_LB_PCS_PARALLEL: PCS RX-to-TX parallel loopback
+ * @MXL862XX_XPCS_LB_PMA_SERIAL: PMA TX-to-RX serial loopback
+ * @MXL862XX_XPCS_LB_PMA_PARALLEL: PMA RX-to-TX parallel loopback
+ */
+enum mxl862xx_xpcs_loopback_mode {
+ MXL862XX_XPCS_LB_DISABLE = 0,
+ MXL862XX_XPCS_LB_PCS_SERIAL = 1,
+ MXL862XX_XPCS_LB_PCS_PARALLEL = 2,
+ MXL862XX_XPCS_LB_PMA_SERIAL = 3,
+ MXL862XX_XPCS_LB_PMA_PARALLEL = 4,
+};
+
+/**
+ * enum mxl862xx_xpcs_reset_type - XPCS reset type
+ * @MXL862XX_XPCS_RESET_VR: vendor-specific reset (fast)
+ * @MXL862XX_XPCS_RESET_SOFT: PCS soft reset
+ * @MXL862XX_XPCS_RESET_HARD: full hardware reset
+ */
+enum mxl862xx_xpcs_reset_type {
+ MXL862XX_XPCS_RESET_VR = 0,
+ MXL862XX_XPCS_RESET_SOFT = 1,
+ MXL862XX_XPCS_RESET_HARD = 2,
+};
+
+/**
+ * struct mxl862xx_xpcs_pcs_cfg - PCS configuration parameters
+ * @port_id: XPCS port index (0-3)
+ * @interface: PCS interface mode. See &enum mxl862xx_xpcs_if_mode
+ * @neg_mode: PCS negotiation mode. See &enum mxl862xx_xpcs_neg_mode
+ * @permit_pause: Allow pause to MAC
+ * @usx_lane_mode: USXGMII lane mode.
+ * See &enum mxl862xx_xpcs_usx_lane_mode
+ * @role: PCS protocol role. See &enum mxl862xx_xpcs_role
+ * @__rsv: reserved
+ * @advertising: AN code word the local end transmits. The active union
+ * member is selected by @interface (and, for the asymmetric
+ * protocols, by @role). Ignored when the local end does
+ * not transmit an AN word (role=MAC for SGMII/QSGMII/
+ * USXGMII, 10GBASE-R, 5GBASE-R) or when @neg_mode is not
+ * INBAND_AN_ON. Pass all-zero to keep the firmware default
+ * advertisement.
+ * @result: Firmware result. >0 means the host must follow with an AN
+ * restart, 0 means no host follow-up is needed, <0 is an errno.
+ */
+struct mxl862xx_xpcs_pcs_cfg {
+ u8 port_id:2;
+ u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
+ u8 neg_mode:2; /* enum mxl862xx_xpcs_neg_mode */
+ u8 permit_pause:1;
+ u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
+ u8 role:1; /* enum mxl862xx_xpcs_role */
+ u8 __rsv:2;
+ union mxl862xx_xpcs_an_word advertising;
+ __le16 result;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_pcs_state - PCS link state
+ * @port_id: XPCS port index (0-3) (input)
+ * @interface: PCS interface mode (input).
+ * See &enum mxl862xx_xpcs_if_mode
+ * @usx_lane_mode: USX lane mode (input)
+ * @usx_subport: USX sub-port 0-3 (input)
+ * @link: Link up (1) / down (0) (output)
+ * @an_complete: Auto-negotiation complete (output)
+ * @duplex: Duplex mode (output). See &enum mxl862xx_xpcs_duplex
+ * @pcs_fault: PCS fault (output)
+ * @pause: Pause negotiation result, bit 0 symmetric, bit 1 asymmetric
+ * (output)
+ * @lp_eee_cap: Link partner supports EEE (output)
+ * @lp_eee_cs_cap: Link partner supports EEE clock-stop (output)
+ * @__rsv: reserved
+ * @__pad: padding
+ * @speed: Resolved speed (output). See &enum mxl862xx_xpcs_speed
+ * @lpa: Link partner ability word (output). Same union as
+ * &union mxl862xx_xpcs_an_word; the host picks the member based on
+ * @interface.
+ */
+struct mxl862xx_xpcs_pcs_state {
+ u8 port_id:2;
+ u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
+ u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
+ u8 usx_subport:2;
+ u8 link:1;
+ u8 an_complete:1;
+ u8 duplex:1; /* enum mxl862xx_xpcs_duplex */
+ u8 pcs_fault:1;
+ u8 pause:2;
+ u8 lp_eee_cap:1;
+ u8 lp_eee_cs_cap:1;
+ u8 __rsv:4;
+ u8 __pad;
+ __le16 speed; /* enum mxl862xx_xpcs_speed */
+ union mxl862xx_xpcs_an_word lpa;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_pcs_disable - PCS disable parameters
+ * @port_id: XPCS port index
+ * @__pad: padding
+ * @result: Firmware result. 0 on success, <0 on error.
+ *
+ * Asserts IDDQ + PHY + XPCS resets to power down the SERDES when the
+ * port is admin-down or no module is plugged in. The next PCS config
+ * implicitly powers it back up and reprograms the desired interface.
+ */
+struct mxl862xx_xpcs_pcs_disable {
+ u8 port_id;
+ u8 __pad;
+ __le16 result;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_an_restart - AN restart parameters
+ * @port_id: XPCS port index (0-3)
+ * @interface: PCS interface mode. See &enum mxl862xx_xpcs_if_mode
+ * @usx_lane_mode: USX lane mode
+ * @__rsv: reserved
+ * @result: Firmware result. 0 on success, <0 on error.
+ *
+ * Restarts auto-negotiation on the given XPCS port. The SERDES must
+ * already be configured.
+ */
+struct mxl862xx_xpcs_an_restart {
+ u8 port_id:2;
+ u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
+ u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
+ u8 __rsv:6;
+ __le16 result;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_pcs_link_up - PCS link-up parameters
+ * @port_id: XPCS port index (0-3)
+ * @interface: PCS interface mode. See &enum mxl862xx_xpcs_if_mode
+ * @duplex: Duplex mode. See &enum mxl862xx_xpcs_duplex
+ * @usx_lane_mode: USX lane mode (USXGMII only; ignored otherwise).
+ * See &enum mxl862xx_xpcs_usx_lane_mode
+ * @usx_subport: USX sub-port 0-3 (QUSXGMII only; ignored otherwise)
+ * @__rsv0: reserved
+ * @speed: Resolved speed. See &enum mxl862xx_xpcs_speed
+ * @result: Firmware result. 0 on success, <0 is errno.
+ *
+ * Called once per link-up event after the host has resolved the
+ * line-side speed/duplex (from the PHY's read_status, from a preceding
+ * PCS get-state, or from a fixed-link description).
+ */
+struct mxl862xx_xpcs_pcs_link_up {
+ u8 port_id:2;
+ u8 interface:6; /* enum mxl862xx_xpcs_if_mode */
+ u8 duplex:1; /* enum mxl862xx_xpcs_duplex */
+ u8 usx_lane_mode:2; /* enum mxl862xx_xpcs_usx_lane_mode */
+ u8 usx_subport:2;
+ u8 __rsv0:3;
+ __le16 speed; /* enum mxl862xx_xpcs_speed */
+ __le16 result;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_loopback_cfg - loopback control
+ * @port_id: XPCS port index
+ * @mode: loopback mode. See &enum mxl862xx_xpcs_loopback_mode
+ * @result: firmware result
+ */
+struct mxl862xx_xpcs_loopback_cfg {
+ u8 port_id;
+ u8 mode; /* enum mxl862xx_xpcs_loopback_mode */
+ __le16 result;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_reset_cfg - XPCS reset
+ * @port_id: XPCS port index
+ * @reset_type: reset type. See &enum mxl862xx_xpcs_reset_type
+ * @result: firmware result
+ */
+struct mxl862xx_xpcs_reset_cfg {
+ u8 port_id;
+ u8 reset_type; /* enum mxl862xx_xpcs_reset_type */
+ __le16 result;
+} __packed;
+
#endif /* __MXL862XX_API_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index f1ea40aa7ea0..c87a955c13c4 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -24,6 +24,7 @@
#define MXL862XX_SS_MAGIC 0x1600
#define GPY_GPY2XX_MAGIC 0x1800
#define SYS_MISC_MAGIC 0x1900
+#define MXL862XX_XPCS_MAGIC 0x1a00
#define MXL862XX_COMMON_CFGGET (MXL862XX_COMMON_MAGIC + 0x9)
#define MXL862XX_COMMON_CFGSET (MXL862XX_COMMON_MAGIC + 0xa)
@@ -71,6 +72,14 @@
#define SYS_MISC_FW_VERSION (SYS_MISC_MAGIC + 0x2)
+#define MXL862XX_XPCS_PCS_CONFIG (MXL862XX_XPCS_MAGIC + 0x1)
+#define MXL862XX_XPCS_PCS_GET_STATE (MXL862XX_XPCS_MAGIC + 0x2)
+#define MXL862XX_XPCS_PCS_DISABLE (MXL862XX_XPCS_MAGIC + 0x4)
+#define MXL862XX_XPCS_AN_RESTART (MXL862XX_XPCS_MAGIC + 0x5)
+#define MXL862XX_XPCS_PCS_LINK_UP (MXL862XX_XPCS_MAGIC + 0x7)
+#define MXL862XX_XPCS_LOOPBACK (MXL862XX_XPCS_MAGIC + 0x8)
+#define MXL862XX_XPCS_RESET (MXL862XX_XPCS_MAGIC + 0x9)
+
#define MMD_API_MAXIMUM_ID 0x7fff
#endif /* __MXL862XX_CMD_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
index f17c429d1f1d..f99c69984357 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
@@ -11,6 +11,9 @@
#include <net/dsa.h>
#include "mxl862xx.h"
+#include "mxl862xx-api.h"
+#include "mxl862xx-cmd.h"
+#include "mxl862xx-host.h"
#include "mxl862xx-phylink.h"
void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -19,8 +22,306 @@ void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
config->mac_capabilities = MAC_ASYM_PAUSE | MAC_SYM_PAUSE | MAC_10 |
MAC_100 | MAC_1000 | MAC_2500FD;
- __set_bit(PHY_INTERFACE_MODE_INTERNAL,
- config->supported_interfaces);
+ switch (port) {
+ case 1 ... 8:
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ break;
+ case 9:
+ case 13:
+ __set_bit(PHY_INTERFACE_MODE_SGMII, config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_10GKR, config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII, config->supported_interfaces);
+ fallthrough;
+ case 10 ... 12:
+ case 14 ... 16:
+ __set_bit(PHY_INTERFACE_MODE_QSGMII, config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_10G_QXGMII, config->supported_interfaces);
+
+ break;
+ default:
+ break;
+ }
+
+ if (port == 9 || port == 13)
+ config->mac_capabilities |= MAC_10000FD | MAC_5000FD;
+}
+
+static struct mxl862xx_pcs *pcs_to_mxl862xx_pcs(struct phylink_pcs *pcs)
+{
+ return container_of(pcs, struct mxl862xx_pcs, pcs);
+}
+
+static int mxl862xx_xpcs_if_mode(phy_interface_t interface)
+{
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ return MXL862XX_XPCS_IF_SGMII;
+ case PHY_INTERFACE_MODE_QSGMII:
+ return MXL862XX_XPCS_IF_QSGMII;
+ case PHY_INTERFACE_MODE_1000BASEX:
+ return MXL862XX_XPCS_IF_1000BASEX;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return MXL862XX_XPCS_IF_2500BASEX;
+ case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_10G_QXGMII:
+ return MXL862XX_XPCS_IF_USXGMII;
+ case PHY_INTERFACE_MODE_10GBASER:
+ return MXL862XX_XPCS_IF_10GBASER;
+ case PHY_INTERFACE_MODE_10GKR:
+ return MXL862XX_XPCS_IF_10GKR;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mxl862xx_xpcs_neg_mode(unsigned int neg_mode)
+{
+ if (!(neg_mode & PHYLINK_PCS_NEG_INBAND))
+ return MXL862XX_XPCS_NEG_NONE;
+ if (neg_mode & PHYLINK_PCS_NEG_ENABLED)
+ return MXL862XX_XPCS_NEG_INBAND_AN_ON;
+ return MXL862XX_XPCS_NEG_INBAND_AN_OFF;
+}
+
+static void mxl862xx_pcs_disable(struct phylink_pcs *pcs)
+{
+ struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
+ struct mxl862xx_priv *priv = mpcs->priv;
+ struct mxl862xx_xpcs_pcs_disable dis = {};
+
+ if (mpcs->slot != 0)
+ return;
+
+ dis.port_id = mpcs->serdes_id;
+
+ MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_DISABLE, dis);
+}
+
+static int mxl862xx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface,
+ const unsigned long *advertising,
+ bool permit_pause_to_mac)
+{
+ struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
+ struct mxl862xx_priv *priv = mpcs->priv;
+ struct mxl862xx_xpcs_pcs_cfg cfg = {};
+ int if_mode, ret;
+ u16 adv;
+
+ if (mpcs->slot != 0)
+ return 0;
+
+ if_mode = mxl862xx_xpcs_if_mode(interface);
+ if (if_mode < 0) {
+ dev_err(priv->ds->dev, "unsupported interface: %s\n",
+ phy_modes(interface));
+ return if_mode;
+ }
+
+ mpcs->if_mode = if_mode;
+
+ cfg.port_id = mpcs->serdes_id;
+ cfg.usx_lane_mode = (interface == PHY_INTERFACE_MODE_10G_QXGMII) ?
+ MXL862XX_XPCS_USX_QUAD : MXL862XX_XPCS_USX_SINGLE;
+ cfg.interface = if_mode;
+ cfg.neg_mode = mxl862xx_xpcs_neg_mode(neg_mode);
+ cfg.role = MXL862XX_XPCS_ROLE_MAC;
+ cfg.permit_pause = permit_pause_to_mac ? 1 : 0;
+
+ if (neg_mode & PHYLINK_PCS_NEG_INBAND) {
+ switch (interface) {
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ adv = linkmode_adv_to_mii_adv_x(advertising,
+ ETHTOOL_LINK_MODE_1000baseX_Full_BIT);
+ cfg.advertising.cl37 = cpu_to_le16(adv);
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ cfg.advertising.cl37 = cpu_to_le16(ADVERTISE_SGMII);
+ break;
+ default:
+ break;
+ }
+ }
+
+ ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_CONFIG, cfg);
+ if (ret)
+ return ret;
+
+ return le16_to_cpu(cfg.result) > 0 ? 1 : 0;
+}
+
+static void mxl862xx_pcs_get_state(struct phylink_pcs *pcs,
+ unsigned int neg_mode,
+ struct phylink_link_state *state)
+{
+ struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
+ struct mxl862xx_priv *priv = mpcs->priv;
+ struct mxl862xx_xpcs_pcs_state st = {};
+ int if_mode, ret;
+ u16 bmsr;
+
+ if_mode = mxl862xx_xpcs_if_mode(state->interface);
+ if (if_mode < 0)
+ return;
+
+ st.port_id = mpcs->serdes_id;
+ st.interface = if_mode;
+ st.usx_subport = mpcs->slot;
+
+ ret = MXL862XX_API_READ(priv, MXL862XX_XPCS_PCS_GET_STATE, st);
+ if (ret)
+ return;
+
+ state->link = st.link && !st.pcs_fault;
+ state->an_complete = st.an_complete;
+
+ switch (state->interface) {
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ bmsr = (state->link ? BMSR_LSTATUS : 0) |
+ (state->an_complete ? BMSR_ANEGCOMPLETE : 0);
+ phylink_mii_c22_pcs_decode_state(state, neg_mode, bmsr,
+ le16_to_cpu(st.lpa.cl37));
+ break;
+
+ case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_10G_QXGMII:
+ if (state->link)
+ phylink_decode_usxgmii_word(state,
+ le16_to_cpu(st.lpa.usx));
+ break;
+
+ case PHY_INTERFACE_MODE_10GBASER:
+ case PHY_INTERFACE_MODE_10GKR:
+ if (state->link) {
+ state->speed = SPEED_10000;
+ state->duplex = DUPLEX_FULL;
+ }
+ break;
+
+ default:
+ state->link = false;
+ break;
+ }
+}
+
+static void mxl862xx_pcs_an_restart(struct phylink_pcs *pcs)
+{
+ struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
+ struct mxl862xx_priv *priv = mpcs->priv;
+ struct mxl862xx_xpcs_an_restart an = {};
+
+ if (mpcs->slot != 0)
+ return;
+
+ an.port_id = mpcs->serdes_id;
+ an.interface = mpcs->if_mode;
+
+ MXL862XX_API_WRITE(priv, MXL862XX_XPCS_AN_RESTART, an);
+}
+
+static void mxl862xx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface, int speed,
+ int duplex)
+{
+ struct mxl862xx_pcs *mpcs = pcs_to_mxl862xx_pcs(pcs);
+ struct mxl862xx_priv *priv = mpcs->priv;
+ struct mxl862xx_xpcs_pcs_link_up lu = {};
+ int if_mode;
+
+ if (mpcs->slot != 0)
+ return;
+
+ if_mode = mxl862xx_xpcs_if_mode(interface);
+ if (if_mode < 0)
+ return;
+
+ lu.port_id = mpcs->serdes_id;
+ lu.interface = if_mode;
+ lu.duplex = (duplex == DUPLEX_FULL) ? MXL862XX_XPCS_DUPLEX_FULL :
+ MXL862XX_XPCS_DUPLEX_HALF;
+ lu.speed = cpu_to_le16(speed);
+
+ MXL862XX_API_WRITE(priv, MXL862XX_XPCS_PCS_LINK_UP, lu);
+}
+
+static unsigned int mxl862xx_pcs_inband_caps(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_QSGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
+ case PHY_INTERFACE_MODE_USXGMII:
+ case PHY_INTERFACE_MODE_10G_QXGMII:
+ case PHY_INTERFACE_MODE_10GKR:
+ return LINK_INBAND_ENABLE;
+ case PHY_INTERFACE_MODE_10GBASER:
+ return LINK_INBAND_DISABLE;
+ default:
+ return 0;
+ }
+}
+
+static const struct phylink_pcs_ops mxl862xx_pcs_ops = {
+ .pcs_disable = mxl862xx_pcs_disable,
+ .pcs_config = mxl862xx_pcs_config,
+ .pcs_get_state = mxl862xx_pcs_get_state,
+ .pcs_an_restart = mxl862xx_pcs_an_restart,
+ .pcs_link_up = mxl862xx_pcs_link_up,
+ .pcs_inband_caps = mxl862xx_pcs_inband_caps,
+};
+
+void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
+ int port)
+{
+ pcs->priv = priv;
+ pcs->serdes_id = MXL862XX_SERDES_PORT_ID(port);
+ pcs->slot = MXL862XX_SERDES_SLOT(port);
+
+ pcs->pcs.ops = &mxl862xx_pcs_ops;
+ pcs->pcs.poll = true;
+
+ __set_bit(PHY_INTERFACE_MODE_QSGMII, pcs->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_10G_QXGMII, pcs->pcs.supported_interfaces);
+ if (pcs->slot != 0)
+ return;
+
+ __set_bit(PHY_INTERFACE_MODE_SGMII, pcs->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_1000BASEX, pcs->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX, pcs->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_10GBASER, pcs->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_10GKR, pcs->pcs.supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_USXGMII, pcs->pcs.supported_interfaces);
+}
+
+static struct phylink_pcs *
+mxl862xx_phylink_mac_select_pcs(struct phylink_config *config,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct mxl862xx_priv *priv = dp->ds->priv;
+ int port = dp->index;
+
+ if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 84))
+ return NULL;
+
+ switch (port) {
+ case 9 ... 16:
+ return &priv->serdes_ports[port - 9].pcs;
+ default:
+ return NULL;
+ }
}
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -48,4 +349,5 @@ const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
.mac_config = mxl862xx_phylink_mac_config,
.mac_link_down = mxl862xx_phylink_mac_link_down,
.mac_link_up = mxl862xx_phylink_mac_link_up,
+ .mac_select_pcs = mxl862xx_phylink_mac_select_pcs,
};
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
index c3d5215bdf60..54a4c652ec5a 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
@@ -7,8 +7,18 @@
#include "mxl862xx.h"
+#define MXL862XX_SERDES_SLOT(port) \
+ (((port) - MXL862XX_FIRST_SERDES_PORT) % MXL862XX_SERDES_SLOTS)
+#define MXL862XX_SERDES_PORT_ID(port) \
+ (((port) - MXL862XX_FIRST_SERDES_PORT) / MXL862XX_SERDES_SLOTS)
+#define MXL862XX_PCS_PORT(mpcs) \
+ (MXL862XX_FIRST_SERDES_PORT + \
+ (mpcs)->serdes_id * MXL862XX_SERDES_SLOTS + (mpcs)->slot)
+
extern const struct phylink_mac_ops mxl862xx_phylink_mac_ops;
void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config);
+void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
+ int port);
#endif /* __MXL862XX_PHYLINK_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 0b1a23364eb5..0af41efccbc6 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -622,7 +622,7 @@ static int mxl862xx_setup(struct dsa_switch *ds)
int n_user_ports = 0, max_vlans;
int ingress_finals, vid_rules;
struct dsa_port *dp;
- int ret;
+ int ret, i;
ret = mxl862xx_reset(priv);
if (ret)
@@ -632,6 +632,10 @@ static int mxl862xx_setup(struct dsa_switch *ds)
if (ret)
return ret;
+ for (i = 0; i < ARRAY_SIZE(priv->serdes_ports); i++)
+ mxl862xx_setup_pcs(priv, &priv->serdes_ports[i],
+ i + MXL862XX_FIRST_SERDES_PORT);
+
/* Calculate Extended VLAN block sizes.
* With VLAN Filter handling VID membership checks:
* Ingress: only final catchall rules (PVID insertion, 802.1Q
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 79fd32c4db4e..1c75bb078a9a 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -11,6 +11,9 @@
struct mxl862xx_priv;
#define MXL862XX_MAX_PORTS 17
+#define MXL862XX_FIRST_SERDES_PORT 9
+#define MXL862XX_SERDES_SLOTS 4
+
#define MXL862XX_DEFAULT_BRIDGE 0
#define MXL862XX_MAX_BRIDGES 48
#define MXL862XX_MAX_BRIDGE_PORTS 128
@@ -242,6 +245,22 @@ struct mxl862xx_port {
spinlock_t stats_lock; /* protects stats accumulators */
};
+/**
+ * struct mxl862xx_pcs - link SerDes interfaces to bridge ports
+ * @pcs: &struct phylink_pcs instance
+ * @priv: pointer to &struct mxl862xx_priv
+ * @serdes_id: SerDes instance index (0 or 1)
+ * @slot: slot within the SerDes (0-3 for QSGMII/QUSXGMII, 0 otherwise)
+ * @if_mode: cached firmware interface mode (enum mxl862xx_xpcs_if_mode)
+ */
+struct mxl862xx_pcs {
+ struct phylink_pcs pcs;
+ struct mxl862xx_priv *priv;
+ int serdes_id;
+ int slot;
+ int if_mode;
+};
+
/**
* union mxl862xx_fw_version - firmware version for comparison and display
* @major: firmware major version
@@ -293,6 +312,8 @@ union mxl862xx_fw_version {
* flooding)
* @fw_version: cached firmware version, populated at probe and
* compared with MXL862XX_FW_VER_MIN()
+ * @serdes_ports: SerDes interfaces incl. sub-interfaces in case of
+ * 10G_QXGMII or QSGMII
* @ports: per-port state, indexed by switch port number
* @bridges: maps DSA bridge number to firmware bridge ID;
* zero means no firmware bridge allocated for that
@@ -311,6 +332,7 @@ struct mxl862xx_priv {
unsigned long flags;
u16 drop_meter;
union mxl862xx_fw_version fw_version;
+ struct mxl862xx_pcs serdes_ports[8];
struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
u16 bridges[MXL862XX_MAX_BRIDGES + 1];
u16 evlan_ingress_size;
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH net-next 5/5] net: dsa: mxl862xx: add SerDes ethtool statistics
2026-05-19 17:38 [PATCH net-next 0/5] net: dsa: mxl862xx: SerDes ports and stats Daniel Golle
` (3 preceding siblings ...)
2026-05-19 17:39 ` [PATCH net-next 4/5] net: dsa: mxl862xx: add support for SerDes ports Daniel Golle
@ 2026-05-19 17:39 ` Daniel Golle
2026-05-19 18:40 ` Vladimir Oltean
4 siblings, 1 reply; 8+ messages in thread
From: Daniel Golle @ 2026-05-19 17:39 UTC (permalink / raw)
To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Russell King,
linux-kernel, netdev
Expose SerDes equalization and signal detect parameters as ethtool
statistics on ports 9-16 (XPCS-backed ports). Uses the XPCS EQ_GET
and SIGNAL_DETECT firmware commands to read TX/RX equalization
coefficients, DFE taps, and link-level signal status.
The 19 additional stats (serdes_tx_*, serdes_rx_*, serdes_pma_link,
serdes_link_fault, serdes_in_reset) are appended after the standard
RMON counters and gated on firmware >= 1.0.84.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 90 ++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 2 +
drivers/net/dsa/mxl862xx/mxl862xx-phylink.c | 93 +++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-phylink.h | 3 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 6 +-
5 files changed, 193 insertions(+), 1 deletion(-)
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
index 6e332c99b245..4cd5d3f0b266 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -1668,4 +1668,94 @@ struct mxl862xx_xpcs_reset_cfg {
__le16 result;
} __packed;
+/**
+ * struct mxl862xx_xpcs_eq_item - single equalization parameter
+ * @value: current initial value
+ * @ovrd: override value
+ * @ovrd_en: override enable flag
+ */
+struct mxl862xx_xpcs_eq_item {
+ u8 value;
+ u8 ovrd;
+ u8 ovrd_en;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_tx_eq_info - TX equalization status
+ * @main: TX main cursor (0-63)
+ * @pre: TX pre-cursor (0-63)
+ * @post: TX post-cursor (0-63)
+ * @iboost_lvl: TX iboost level (0-15)
+ * @vboost_lvl: TX vboost level (0-7)
+ * @vboost_en: TX vboost enable (0-1)
+ */
+struct mxl862xx_xpcs_tx_eq_info {
+ struct mxl862xx_xpcs_eq_item main;
+ struct mxl862xx_xpcs_eq_item pre;
+ struct mxl862xx_xpcs_eq_item post;
+ struct mxl862xx_xpcs_eq_item iboost_lvl;
+ struct mxl862xx_xpcs_eq_item vboost_lvl;
+ struct mxl862xx_xpcs_eq_item vboost_en;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_rx_eq_info - RX equalization status
+ * @att_lvl: RX attenuation level (0-7)
+ * @vga1_gain: RX VGA1 gain (0-7)
+ * @vga2_gain: RX VGA2 gain (0-7)
+ * @ctle_boost: RX CTLE boost (0-31)
+ * @ctle_pole: RX CTLE pole (0-3)
+ * @dfe_tap1: RX DFE tap1 (0-255)
+ * @dfe_bypass: RX DFE bypass (0-1)
+ * @adapt_mode: RX adapt mode (0-3)
+ * @adapt_sel: RX adapt select (0-1)
+ */
+struct mxl862xx_xpcs_rx_eq_info {
+ struct mxl862xx_xpcs_eq_item att_lvl;
+ struct mxl862xx_xpcs_eq_item vga1_gain;
+ struct mxl862xx_xpcs_eq_item vga2_gain;
+ struct mxl862xx_xpcs_eq_item ctle_boost;
+ struct mxl862xx_xpcs_eq_item ctle_pole;
+ struct mxl862xx_xpcs_eq_item dfe_tap1;
+ struct mxl862xx_xpcs_eq_item dfe_bypass;
+ struct mxl862xx_xpcs_eq_item adapt_mode;
+ struct mxl862xx_xpcs_eq_item adapt_sel;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_eq_get - EQ get request/response
+ * @port_id: XPCS port index (0 or 1)
+ * @result: firmware result
+ * @tx: TX equalization info
+ * @rx: RX equalization info
+ */
+struct mxl862xx_xpcs_eq_get {
+ u8 port_id;
+ __le16 result;
+ struct mxl862xx_xpcs_tx_eq_info tx;
+ struct mxl862xx_xpcs_rx_eq_info rx;
+} __packed;
+
+/**
+ * struct mxl862xx_xpcs_signal_detect - signal detect status
+ * @port_id: XPCS port index (0 or 1)
+ * @rx_signal: RX signal detected
+ * @pma_link: PMA link up
+ * @link_fault: PCS link fault
+ * @in_reset: XPCS in reset
+ * @__rsv: reserved
+ * @__pad: padding
+ * @result: firmware result
+ */
+struct mxl862xx_xpcs_signal_detect {
+ u8 port_id:2;
+ u8 rx_signal:1;
+ u8 pma_link:1;
+ u8 link_fault:1;
+ u8 in_reset:1;
+ u8 __rsv:2;
+ u8 __pad;
+ __le16 result;
+} __packed;
+
#endif /* __MXL862XX_API_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index c87a955c13c4..f6fa32bac5d8 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -79,6 +79,8 @@
#define MXL862XX_XPCS_PCS_LINK_UP (MXL862XX_XPCS_MAGIC + 0x7)
#define MXL862XX_XPCS_LOOPBACK (MXL862XX_XPCS_MAGIC + 0x8)
#define MXL862XX_XPCS_RESET (MXL862XX_XPCS_MAGIC + 0x9)
+#define MXL862XX_XPCS_EQ_GET (MXL862XX_XPCS_MAGIC + 0xc)
+#define MXL862XX_XPCS_SIGNAL_DETECT (MXL862XX_XPCS_MAGIC + 0xd)
#define MMD_API_MAXIMUM_ID 0x7fff
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
index f99c69984357..c5ee07a444e9 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.c
@@ -351,3 +351,96 @@ const struct phylink_mac_ops mxl862xx_phylink_mac_ops = {
.mac_link_up = mxl862xx_phylink_mac_link_up,
.mac_select_pcs = mxl862xx_phylink_mac_select_pcs,
};
+
+/* --- SerDes ethtool statistics --- */
+
+static const char mxl862xx_serdes_stats[][ETH_GSTRING_LEN] = {
+ "serdes_tx_main",
+ "serdes_tx_pre",
+ "serdes_tx_post",
+ "serdes_tx_iboost",
+ "serdes_tx_vboost",
+ "serdes_tx_vboost_en",
+ "serdes_rx_att",
+ "serdes_rx_vga1",
+ "serdes_rx_vga2",
+ "serdes_rx_ctle_boost",
+ "serdes_rx_ctle_pole",
+ "serdes_rx_dfe_tap1",
+ "serdes_rx_dfe_bypass",
+ "serdes_rx_adapt_mode",
+ "serdes_rx_adapt_sel",
+ "serdes_rx_signal",
+ "serdes_pma_link",
+ "serdes_link_fault",
+ "serdes_in_reset",
+};
+
+static bool mxl862xx_port_has_serdes_stats(struct dsa_switch *ds, int port)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+
+ return port >= 9 && port <= 16 &&
+ MXL862XX_FW_VER_MIN(priv, 1, 0, 84);
+}
+
+int mxl862xx_serdes_stats_count(struct dsa_switch *ds, int port)
+{
+ if (mxl862xx_port_has_serdes_stats(ds, port))
+ return ARRAY_SIZE(mxl862xx_serdes_stats);
+
+ return 0;
+}
+
+void mxl862xx_serdes_get_strings(struct dsa_switch *ds, int port, u8 *data)
+{
+ int i;
+
+ if (!mxl862xx_port_has_serdes_stats(ds, port))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(mxl862xx_serdes_stats); i++)
+ ethtool_puts(&data, mxl862xx_serdes_stats[i]);
+}
+
+void mxl862xx_serdes_get_stats(struct dsa_switch *ds, int port, u64 *data)
+{
+ struct mxl862xx_xpcs_eq_get eq = {
+ .port_id = MXL862XX_SERDES_PORT_ID(port),
+ };
+ struct mxl862xx_xpcs_signal_detect sig = {};
+
+ if (!mxl862xx_port_has_serdes_stats(ds, port))
+ return;
+
+ sig.port_id = MXL862XX_SERDES_PORT_ID(port);
+
+ if (!MXL862XX_API_READ(ds->priv, MXL862XX_XPCS_EQ_GET, eq)) {
+ *data++ = eq.tx.main.value;
+ *data++ = eq.tx.pre.value;
+ *data++ = eq.tx.post.value;
+ *data++ = eq.tx.iboost_lvl.value;
+ *data++ = eq.tx.vboost_lvl.value;
+ *data++ = eq.tx.vboost_en.value;
+ *data++ = eq.rx.att_lvl.value;
+ *data++ = eq.rx.vga1_gain.value;
+ *data++ = eq.rx.vga2_gain.value;
+ *data++ = eq.rx.ctle_boost.value;
+ *data++ = eq.rx.ctle_pole.value;
+ *data++ = eq.rx.dfe_tap1.value;
+ *data++ = eq.rx.dfe_bypass.value;
+ *data++ = eq.rx.adapt_mode.value;
+ *data++ = eq.rx.adapt_sel.value;
+ } else {
+ data += 15;
+ }
+
+ if (!MXL862XX_API_READ(ds->priv, MXL862XX_XPCS_SIGNAL_DETECT, sig)) {
+ *data++ = sig.rx_signal;
+ *data++ = sig.pma_link;
+ *data++ = sig.link_fault;
+ *data++ = sig.in_reset;
+ } else {
+ data += 4;
+ }
+}
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
index 54a4c652ec5a..82cc3817adc1 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-phylink.h
@@ -20,5 +20,8 @@ void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
struct phylink_config *config);
void mxl862xx_setup_pcs(struct mxl862xx_priv *priv, struct mxl862xx_pcs *pcs,
int port);
+int mxl862xx_serdes_stats_count(struct dsa_switch *ds, int port);
+void mxl862xx_serdes_get_strings(struct dsa_switch *ds, int port, u8 *data);
+void mxl862xx_serdes_get_stats(struct dsa_switch *ds, int port, u64 *data);
#endif /* __MXL862XX_PHYLINK_H */
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 0af41efccbc6..a18e42b9cd6b 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -1776,6 +1776,8 @@ static void mxl862xx_get_strings(struct dsa_switch *ds, int port,
for (i = 0; i < ARRAY_SIZE(mxl862xx_mib); i++)
ethtool_puts(&data, mxl862xx_mib[i].name);
+
+ mxl862xx_serdes_get_strings(ds, port, data);
}
static int mxl862xx_get_sset_count(struct dsa_switch *ds, int port, int sset)
@@ -1783,7 +1785,7 @@ static int mxl862xx_get_sset_count(struct dsa_switch *ds, int port, int sset)
if (sset != ETH_SS_STATS)
return 0;
- return ARRAY_SIZE(mxl862xx_mib);
+ return ARRAY_SIZE(mxl862xx_mib) + mxl862xx_serdes_stats_count(ds, port);
}
static int mxl862xx_read_rmon(struct dsa_switch *ds, int port,
@@ -1819,6 +1821,8 @@ static void mxl862xx_get_ethtool_stats(struct dsa_switch *ds, int port,
else
*data++ = le64_to_cpu(*(__le64 *)field);
}
+
+ mxl862xx_serdes_get_stats(ds, port, data);
}
static void mxl862xx_get_eth_mac_stats(struct dsa_switch *ds, int port,
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH net-next 5/5] net: dsa: mxl862xx: add SerDes ethtool statistics
2026-05-19 17:39 ` [PATCH net-next 5/5] net: dsa: mxl862xx: add SerDes ethtool statistics Daniel Golle
@ 2026-05-19 18:40 ` Vladimir Oltean
2026-05-19 22:54 ` Daniel Golle
0 siblings, 1 reply; 8+ messages in thread
From: Vladimir Oltean @ 2026-05-19 18:40 UTC (permalink / raw)
To: Daniel Golle
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, linux-kernel, netdev
On Tue, May 19, 2026 at 06:39:29PM +0100, Daniel Golle wrote:
> +/* --- SerDes ethtool statistics --- */
> +
> +static const char mxl862xx_serdes_stats[][ETH_GSTRING_LEN] = {
> + "serdes_tx_main",
> + "serdes_tx_pre",
> + "serdes_tx_post",
> + "serdes_tx_iboost",
> + "serdes_tx_vboost",
> + "serdes_tx_vboost_en",
> + "serdes_rx_att",
> + "serdes_rx_vga1",
> + "serdes_rx_vga2",
> + "serdes_rx_ctle_boost",
> + "serdes_rx_ctle_pole",
> + "serdes_rx_dfe_tap1",
> + "serdes_rx_dfe_bypass",
> + "serdes_rx_adapt_mode",
> + "serdes_rx_adapt_sel",
> + "serdes_rx_signal",
> + "serdes_pma_link",
> + "serdes_link_fault",
> + "serdes_in_reset",
> +};
Which of these are statistics counters, and which of _those_ are
Ethernet-specific (SerDes may be multiprotocol, also supporting PCIe
etc, and the info above is all protocol-agnostic)?
I'm trying to hint at the fact that ethtool is not the right interface
for presenting such information. Intuitively, I would suggest we need to
put head to head various SerDes designs and present to Vinod Koul a
netlink UAPI for monitoring purposes.
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH net-next 5/5] net: dsa: mxl862xx: add SerDes ethtool statistics
2026-05-19 18:40 ` Vladimir Oltean
@ 2026-05-19 22:54 ` Daniel Golle
0 siblings, 0 replies; 8+ messages in thread
From: Daniel Golle @ 2026-05-19 22:54 UTC (permalink / raw)
To: Vladimir Oltean
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Russell King, linux-kernel, netdev
On Tue, May 19, 2026 at 09:40:05PM +0300, Vladimir Oltean wrote:
> On Tue, May 19, 2026 at 06:39:29PM +0100, Daniel Golle wrote:
> > +/* --- SerDes ethtool statistics --- */
> > +
> > +static const char mxl862xx_serdes_stats[][ETH_GSTRING_LEN] = {
> > + "serdes_tx_main",
> > + "serdes_tx_pre",
> > + "serdes_tx_post",
> > + "serdes_tx_iboost",
> > + "serdes_tx_vboost",
> > + "serdes_tx_vboost_en",
> > + "serdes_rx_att",
> > + "serdes_rx_vga1",
> > + "serdes_rx_vga2",
> > + "serdes_rx_ctle_boost",
> > + "serdes_rx_ctle_pole",
> > + "serdes_rx_dfe_tap1",
> > + "serdes_rx_dfe_bypass",
> > + "serdes_rx_adapt_mode",
> > + "serdes_rx_adapt_sel",
> > + "serdes_rx_signal",
> > + "serdes_pma_link",
> > + "serdes_link_fault",
> > + "serdes_in_reset",
> > +};
>
> Which of these are statistics counters, and which of _those_ are
> Ethernet-specific (SerDes may be multiprotocol, also supporting PCIe
> etc, and the info above is all protocol-agnostic)?
The SerDes is basically a DW XPCS but exposed only via the firmware.
So it's Ethernet-only. This is a external switch IC, and while it does
have a very small CPU, that CPU only has low speed peripherals, no
USB3, PCIe, SATA what-so-ever other SerDes you may think of.
But true, the values themselves are not very Ethernet-specific, most
can probably be used to describe any generic LVDS.
> I'm trying to hint at the fact that ethtool is not the right interface
> for presenting such information. Intuitively, I would suggest we need to
> put head to head various SerDes designs and present to Vinod Koul a
> netlink UAPI for monitoring purposes.
I must admit I kinda expected a push-back on that one, which is also
why I put the patch last in the series, so the rest of the series can
be merged without the stats.
The firmware also exposes self-test via PRBS and BERT, which also
doesn't fit the ethtool self-test model very well (which currently
supports only MAC-level and PHY-level loopback tests, with no way to
represent the PCS/SerDes-level)...
^ permalink raw reply [flat|nested] 8+ messages in thread