From ee868127170c1211c528f0629277d8b44a0e1852 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 10 Jun 2025 11:13:52 +0200 Subject: [PATCH 1/3] net: phy: micrel: add MDI/MDI-X control support for KSZ9477 switch-integrated PHYs Add MDI/MDI-X configuration support for PHYs integrated in the KSZ9477 family of Ethernet switches. All MDI/MDI-X configuration modes are supported: - Automatic MDI/MDI-X (ETH_TP_MDI_AUTO) - Forced MDI (ETH_TP_MDI) - Forced MDI-X (ETH_TP_MDI_X) However, when operating in automatic mode, the PHY does not expose the resolved crossover status (i.e., whether MDI or MDI-X is active). Therefore, in auto mode, the driver reports ETH_TP_MDI_INVALID as the current status. Signed-off-by: Oleksij Rempel Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20250610091354.4060454-2-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski --- drivers/net/phy/micrel.c | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 64aa03aed770..a51010e64444 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -1948,6 +1948,56 @@ static int ksz886x_read_status(struct phy_device *phydev) return genphy_read_status(phydev); } +static int ksz9477_mdix_update(struct phy_device *phydev) +{ + if (phydev->mdix_ctrl != ETH_TP_MDI_AUTO) + phydev->mdix = phydev->mdix_ctrl; + else + phydev->mdix = ETH_TP_MDI_INVALID; + + return 0; +} + +static int ksz9477_read_mdix_ctrl(struct phy_device *phydev) +{ + int val; + + val = phy_read(phydev, MII_KSZ9131_AUTO_MDIX); + if (val < 0) + return val; + + if (!(val & MII_KSZ9131_AUTO_MDIX_SWAP_OFF)) + phydev->mdix_ctrl = ETH_TP_MDI_AUTO; + else if (val & MII_KSZ9131_AUTO_MDI_SET) + phydev->mdix_ctrl = ETH_TP_MDI; + else + phydev->mdix_ctrl = ETH_TP_MDI_X; + + return 0; +} + +static int ksz9477_read_status(struct phy_device *phydev) +{ + int ret; + + ret = ksz9477_mdix_update(phydev); + if (ret) + return ret; + + return genphy_read_status(phydev); +} + +static int ksz9477_config_aneg(struct phy_device *phydev) +{ + int ret; + + ret = ksz9131_config_mdix(phydev, phydev->mdix_ctrl); + if (ret) + return ret; + + return genphy_config_aneg(phydev); +} + struct ksz9477_errata_write { u8 dev_addr; u8 reg_addr; @@ -2029,6 +2079,13 @@ static int ksz9477_config_init(struct phy_device *phydev) return err; } + /* Read initial MDI-X config state. So, we do not need to poll it + * later on. + */ + err = ksz9477_read_mdix_ctrl(phydev); + if (err) + return err; + return kszphy_config_init(phydev); } @@ -5691,6 +5748,8 @@ static struct phy_driver ksphy_driver[] = { /* PHY_GBIT_FEATURES */ .config_init = ksz9477_config_init, .config_intr = kszphy_config_intr, + .config_aneg = ksz9477_config_aneg, + .read_status = ksz9477_read_status, .handle_interrupt = kszphy_handle_interrupt, .suspend = genphy_suspend, .resume = ksz9477_resume, From 597ebdf37222ba7deb2ed24aa4de777d7edf22f7 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 10 Jun 2025 11:13:53 +0200 Subject: [PATCH 2/3] net: phy: micrel: Add RX error counter support for KSZ9477 switch-integrated PHYs Add support for tracking receive error statistics from PHYs integrated into the KSZ9477 family of Ethernet switches. The integrated PHYs expose a receive error (RXER) counter in register 0x15. This counter increments when the PHY detects one or more symbol errors on a received frame. The register is cleared upon reading. Changes include: - `kszphy_update_stats()` to accumulate the RX error count. - `kszphy_get_phy_stats()` to expose this count via ethtool PHY stats. - Addition of a private `rx_err_pkt_cnt` field in the driver. - Registration of `.update_stats` and `.get_phy_stats` callbacks in the KSZ9477 PHY driver structure. The functionality of this counter was confirmed by physically disturbing the signal lines - specifically by wiggling exposed twisted pair wires and intentionally shorting between pairs. These actions triggered RXER increments, validating the counter's behavior. This RXER counter is confirmed for KSZ9477 and likely applicable to other related PHYs like those in KSZ9313. Signed-off-by: Oleksij Rempel Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20250610091354.4060454-3-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski --- drivers/net/phy/micrel.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index a51010e64444..68d86383e6c7 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -431,6 +431,10 @@ struct kszphy_ptp_priv { spinlock_t seconds_lock; }; +struct kszphy_phy_stats { + u64 rx_err_pkt_cnt; +}; + struct kszphy_priv { struct kszphy_ptp_priv ptp_priv; const struct kszphy_type *type; @@ -441,6 +445,7 @@ struct kszphy_priv { bool rmii_ref_clk_sel_val; bool clk_enable; u64 stats[ARRAY_SIZE(kszphy_hw_stats)]; + struct kszphy_phy_stats phy_stats; }; static const struct kszphy_type lan8814_type = { @@ -2130,6 +2135,35 @@ static void kszphy_get_stats(struct phy_device *phydev, data[i] = kszphy_get_stat(phydev, i); } +/* KSZ9477 PHY RXER Counter. Probably supported by other PHYs like KSZ9313, + * etc. The counter is incremented when the PHY receives a frame with one or + * more symbol errors. The counter is cleared when the register is read. + */ +#define MII_KSZ9477_PHY_RXER_COUNTER 0x15 + +static int kszphy_update_stats(struct phy_device *phydev) +{ + struct kszphy_priv *priv = phydev->priv; + int ret; + + ret = phy_read(phydev, MII_KSZ9477_PHY_RXER_COUNTER); + if (ret < 0) + return ret; + + priv->phy_stats.rx_err_pkt_cnt += ret; + + return 0; +} + +static void kszphy_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct kszphy_priv *priv = phydev->priv; + + stats->rx_errors = priv->phy_stats.rx_err_pkt_cnt; +} + static void kszphy_enable_clk(struct phy_device *phydev) { struct kszphy_priv *priv = phydev->priv; @@ -5745,6 +5779,7 @@ static struct phy_driver ksphy_driver[] = { .phy_id = PHY_ID_KSZ9477, .phy_id_mask = MICREL_PHY_ID_MASK, .name = "Microchip KSZ9477", + .probe = kszphy_probe, /* PHY_GBIT_FEATURES */ .config_init = ksz9477_config_init, .config_intr = kszphy_config_intr, @@ -5753,6 +5788,8 @@ static struct phy_driver ksphy_driver[] = { .handle_interrupt = kszphy_handle_interrupt, .suspend = genphy_suspend, .resume = ksz9477_resume, + .get_phy_stats = kszphy_get_phy_stats, + .update_stats = kszphy_update_stats, } }; module_phy_driver(ksphy_driver); From b2f96c3c96314ff3888ebb7d3126cf5f5e7c278b Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Tue, 10 Jun 2025 11:13:54 +0200 Subject: [PATCH 3/3] net: phy: micrel: add cable test support for KSZ9477-class PHYs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable cable test support for KSZ9477-class PHYs by reusing the existing KSZ9131 implementation. This also adds support for 100Mbit-only PHYs like KSZ8563, which are identified as KSZ9477. For these PHYs, only two wire pairs (A and B) are active, so the cable test logic limits the pair_mask accordingly. Support for KSZ8563 is untested but added based on its register compatibility and PHY ID match. Tested on KSZ9893 (Gigabit): open and short conditions were correctly detected on all four pairs. Fault length reporting is functional and varies by pair. For example: - 2m cable: open faults reported ~1.2m (pairs B–D), 0.0m (pair A) - No cable: all pairs report 0.0m fault length Signed-off-by: Oleksij Rempel Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20250610091354.4060454-4-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski --- drivers/net/phy/micrel.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 68d86383e6c7..d0429dc8f561 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -1723,7 +1723,8 @@ static int ksz9x31_cable_test_fault_length(struct phy_device *phydev, u16 stat) * * distance to fault = (VCT_DATA - 22) * 4 / cable propagation velocity */ - if (phydev_id_compare(phydev, PHY_ID_KSZ9131)) + if (phydev_id_compare(phydev, PHY_ID_KSZ9131) || + phydev_id_compare(phydev, PHY_ID_KSZ9477)) dt = clamp(dt - 22, 0, 255); return (dt * 400) / 10; @@ -1797,12 +1798,20 @@ static int ksz9x31_cable_test_get_status(struct phy_device *phydev, bool *finished) { struct kszphy_priv *priv = phydev->priv; - unsigned long pair_mask = 0xf; + unsigned long pair_mask; int retries = 20; int pair, ret, rv; *finished = false; + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + phydev->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + phydev->supported)) + pair_mask = 0xf; /* All pairs */ + else + pair_mask = 0x3; /* Pairs A and B only */ + /* Try harder if link partner is active */ while (pair_mask && retries--) { for_each_set_bit(pair, &pair_mask, 4) { @@ -5790,6 +5799,8 @@ static struct phy_driver ksphy_driver[] = { .resume = ksz9477_resume, .get_phy_stats = kszphy_get_phy_stats, .update_stats = kszphy_update_stats, + .cable_test_start = ksz9x31_cable_test_start, + .cable_test_get_status = ksz9x31_cable_test_get_status, } }; module_phy_driver(ksphy_driver);