diff --git a/drivers/net/ethernet/engleder/tsnep_main.c b/drivers/net/ethernet/engleder/tsnep_main.c index 3de4cb06e2668..625245b0845c7 100644 --- a/drivers/net/ethernet/engleder/tsnep_main.c +++ b/drivers/net/ethernet/engleder/tsnep_main.c @@ -221,20 +221,19 @@ static void tsnep_phy_link_status_change(struct net_device *netdev) static int tsnep_phy_loopback(struct tsnep_adapter *adapter, bool enable) { - int retval; - - retval = phy_loopback(adapter->phydev, enable); + int speed; - /* PHY link state change is not signaled if loopback is enabled, it - * would delay a working loopback anyway, let's ensure that loopback - * is working immediately by setting link mode directly - */ - if (!retval && enable) { - netif_carrier_on(adapter->netdev); - tsnep_set_link_mode(adapter); + if (enable) { + if (adapter->phydev->autoneg == AUTONEG_DISABLE && + adapter->phydev->speed == SPEED_100) + speed = SPEED_100; + else + speed = SPEED_1000; + } else { + speed = 0; } - return retval; + return phy_loopback(adapter->phydev, enable, speed); } static int tsnep_phy_open(struct tsnep_adapter *adapter) diff --git a/drivers/net/ethernet/hisilicon/hns/hns_ethtool.c b/drivers/net/ethernet/hisilicon/hns/hns_ethtool.c index 6c458f037262f..60a586a951a02 100644 --- a/drivers/net/ethernet/hisilicon/hns/hns_ethtool.c +++ b/drivers/net/ethernet/hisilicon/hns/hns_ethtool.c @@ -266,9 +266,9 @@ static int hns_nic_config_phy_loopback(struct phy_device *phy_dev, u8 en) if (err) goto out; - err = phy_loopback(phy_dev, true); + err = phy_loopback(phy_dev, true, 0); } else { - err = phy_loopback(phy_dev, false); + err = phy_loopback(phy_dev, false, 0); if (err) goto out; diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c index 8c736a069eeb4..92f9b8ec76d9f 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_main.c @@ -7875,7 +7875,7 @@ static int hclge_enable_phy_loopback(struct hclge_dev *hdev, if (ret) return ret; - return phy_loopback(phydev, true); + return phy_loopback(phydev, true, 0); } static int hclge_disable_phy_loopback(struct hclge_dev *hdev, @@ -7883,7 +7883,7 @@ static int hclge_disable_phy_loopback(struct hclge_dev *hdev, { int ret; - ret = phy_loopback(phydev, false); + ret = phy_loopback(phydev, false, 0); if (ret) return ret; diff --git a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mdio.c b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mdio.c index 80079657afebe..9a456ebf9b7cd 100644 --- a/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mdio.c +++ b/drivers/net/ethernet/hisilicon/hns3/hns3pf/hclge_mdio.c @@ -258,7 +258,7 @@ void hclge_mac_start_phy(struct hclge_dev *hdev) if (!phydev) return; - phy_loopback(phydev, false); + phy_loopback(phydev, false, 0); phy_start(phydev); } diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c index 3ca1c2a816ff1..a01bc394d1ac4 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_selftests.c @@ -382,14 +382,14 @@ static int stmmac_test_phy_loopback(struct stmmac_priv *priv) if (!priv->dev->phydev) return -EOPNOTSUPP; - ret = phy_loopback(priv->dev->phydev, true); + ret = phy_loopback(priv->dev->phydev, true, 0); if (ret) return ret; attr.dst = priv->dev->dev_addr; ret = __stmmac_test_loopback(priv, &attr); - phy_loopback(priv->dev->phydev, false); + phy_loopback(priv->dev->phydev, false, 0); return ret; } @@ -1985,7 +1985,7 @@ void stmmac_selftest_run(struct net_device *dev, case STMMAC_LOOPBACK_PHY: ret = -EOPNOTSUPP; if (dev->phydev) - ret = phy_loopback(dev->phydev, true); + ret = phy_loopback(dev->phydev, true, 0); if (!ret) break; fallthrough; @@ -2018,7 +2018,7 @@ void stmmac_selftest_run(struct net_device *dev, case STMMAC_LOOPBACK_PHY: ret = -EOPNOTSUPP; if (dev->phydev) - ret = phy_loopback(dev->phydev, false); + ret = phy_loopback(dev->phydev, false, 0); if (!ret) break; fallthrough; diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c index 6bb469429b9d0..bd7a47a903aca 100644 --- a/drivers/net/phy/adin1100.c +++ b/drivers/net/phy/adin1100.c @@ -215,8 +215,11 @@ static int adin_resume(struct phy_device *phydev) return adin_set_powerdown_mode(phydev, false); } -static int adin_set_loopback(struct phy_device *phydev, bool enable) +static int adin_set_loopback(struct phy_device *phydev, bool enable, int speed) { + if (enable && speed) + return -EOPNOTSUPP; + if (enable) return phy_set_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10T1L_CTRL, BMCR_LOOPBACK); diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index c1451df430ace..063266cafe9c7 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -1009,8 +1009,11 @@ static void dp83867_link_change_notify(struct phy_device *phydev) } } -static int dp83867_loopback(struct phy_device *phydev, bool enable) +static int dp83867_loopback(struct phy_device *phydev, bool enable, int speed) { + if (enable && speed) + return -EOPNOTSUPP; + return phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, enable ? BMCR_LOOPBACK : 0); } diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index dd254e36ca8ae..623292948fa70 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -2131,52 +2131,52 @@ static void marvell_get_stats_simple(struct phy_device *phydev, data[i] = marvell_get_stat_simple(phydev, i); } -static int m88e1510_loopback(struct phy_device *phydev, bool enable) +static int m88e1510_loopback(struct phy_device *phydev, bool enable, int speed) { + u16 bmcr_ctl, mscr2_ctl = 0; int err; - if (enable) { - u16 bmcr_ctl, mscr2_ctl = 0; + if (!enable) + return genphy_loopback(phydev, enable, 0); - bmcr_ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex); - - err = phy_write(phydev, MII_BMCR, bmcr_ctl); - if (err < 0) - return err; + if (speed == SPEED_10 || speed == SPEED_100 || speed == SPEED_1000) + phydev->speed = speed; + else if (speed) + return -EINVAL; - if (phydev->speed == SPEED_1000) - mscr2_ctl = BMCR_SPEED1000; - else if (phydev->speed == SPEED_100) - mscr2_ctl = BMCR_SPEED100; + bmcr_ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex); - err = phy_modify_paged(phydev, MII_MARVELL_MSCR_PAGE, - MII_88E1510_MSCR_2, BMCR_SPEED1000 | - BMCR_SPEED100, mscr2_ctl); - if (err < 0) - return err; + err = phy_write(phydev, MII_BMCR, bmcr_ctl); + if (err < 0) + return err; - /* Need soft reset to have speed configuration takes effect */ - err = genphy_soft_reset(phydev); - if (err < 0) - return err; + if (phydev->speed == SPEED_1000) + mscr2_ctl = BMCR_SPEED1000; + else if (phydev->speed == SPEED_100) + mscr2_ctl = BMCR_SPEED100; - err = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, - BMCR_LOOPBACK); + err = phy_modify_paged(phydev, MII_MARVELL_MSCR_PAGE, + MII_88E1510_MSCR_2, BMCR_SPEED1000 | + BMCR_SPEED100, mscr2_ctl); + if (err < 0) + return err; - if (!err) { - /* It takes some time for PHY device to switch - * into/out-of loopback mode. - */ - msleep(1000); - } + /* Need soft reset to have speed configuration takes effect */ + err = genphy_soft_reset(phydev); + if (err < 0) return err; - } else { - err = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, 0); - if (err < 0) - return err; - return phy_config_aneg(phydev); + err = phy_modify(phydev, MII_BMCR, BMCR_LOOPBACK, + BMCR_LOOPBACK); + + if (!err) { + /* + * It takes some time for PHY device to switch into loopback + * mode. + */ + msleep(1000); } + return err; } static int marvell_vct5_wait_complete(struct phy_device *phydev) diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 289e1d56aa655..24882d30f6858 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -1032,6 +1032,29 @@ static int ksz9021_config_init(struct phy_device *phydev) #define MII_KSZ9031RN_EDPD 0x23 #define MII_KSZ9031RN_EDPD_ENABLE BIT(0) +static int ksz9031_set_loopback(struct phy_device *phydev, bool enable, + int speed) +{ + u16 ctl = BMCR_LOOPBACK; + int val; + + if (!enable) + return genphy_loopback(phydev, enable, 0); + + if (speed == SPEED_10 || speed == SPEED_100 || speed == SPEED_1000) + phydev->speed = speed; + else if (speed) + return -EINVAL; + phydev->duplex = DUPLEX_FULL; + + ctl |= mii_bmcr_encode_fixed(phydev->speed, phydev->duplex); + + phy_write(phydev, MII_BMCR, ctl); + + return phy_read_poll_timeout(phydev, MII_BMSR, val, val & BMSR_LSTATUS, + 5000, 500000, true); +} + static int ksz9031_of_load_skew_values(struct phy_device *phydev, const struct device_node *of_node, u16 reg, size_t field_sz, @@ -5565,6 +5588,7 @@ static struct phy_driver ksphy_driver[] = { .resume = kszphy_resume, .cable_test_start = ksz9x31_cable_test_start, .cable_test_get_status = ksz9x31_cable_test_get_status, + .set_loopback = ksz9031_set_loopback, }, { .phy_id = PHY_ID_LAN8814, .phy_id_mask = MICREL_PHY_ID_MASK, diff --git a/drivers/net/phy/mxl-gpy.c b/drivers/net/phy/mxl-gpy.c index 94d9cb727121a..a6cca8d432536 100644 --- a/drivers/net/phy/mxl-gpy.c +++ b/drivers/net/phy/mxl-gpy.c @@ -813,7 +813,7 @@ static void gpy_get_wol(struct phy_device *phydev, wol->wolopts = priv->wolopts; } -static int gpy_loopback(struct phy_device *phydev, bool enable) +static int gpy_loopback(struct phy_device *phydev, bool enable, int speed) { struct gpy_priv *priv = phydev->priv; u16 set = 0; @@ -822,6 +822,9 @@ static int gpy_loopback(struct phy_device *phydev, bool enable) if (enable) { u64 now = get_jiffies_64(); + if (speed) + return -EOPNOTSUPP; + /* wait until 3 seconds from last disable */ if (time_before64(now, priv->lb_dis_to)) msleep(jiffies64_to_msecs(priv->lb_dis_to - now)); @@ -845,15 +848,15 @@ static int gpy_loopback(struct phy_device *phydev, bool enable) return 0; } -static int gpy115_loopback(struct phy_device *phydev, bool enable) +static int gpy115_loopback(struct phy_device *phydev, bool enable, int speed) { struct gpy_priv *priv = phydev->priv; if (enable) - return gpy_loopback(phydev, enable); + return gpy_loopback(phydev, enable, speed); if (priv->fw_minor > 0x76) - return gpy_loopback(phydev, 0); + return gpy_loopback(phydev, 0, 0); return genphy_soft_reset(phydev); } diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index 0bcbdce381071..8a0ffc3174ecf 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -1228,8 +1228,11 @@ int gen10g_config_aneg(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(gen10g_config_aneg); -int genphy_c45_loopback(struct phy_device *phydev, bool enable) +int genphy_c45_loopback(struct phy_device *phydev, bool enable, int speed) { + if (enable && speed) + return -EOPNOTSUPP; + return phy_modify_mmd(phydev, MDIO_MMD_PCS, MDIO_CTRL1, MDIO_PCS_CTRL1_LOOPBACK, enable ? MDIO_PCS_CTRL1_LOOPBACK : 0); diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 562acde892249..13df28445f020 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -1707,6 +1707,93 @@ void phy_mac_interrupt(struct phy_device *phydev) } EXPORT_SYMBOL(phy_mac_interrupt); +/** + * phy_loopback - Configure loopback mode of PHY + * @phydev: target phy_device struct + * @enable: enable or disable loopback mode + * @speed: enable loopback mode with speed + * + * Configure loopback mode of PHY and signal link down and link up if speed is + * changing. + * + * Return: 0 on success, negative error code on failure. + */ +int phy_loopback(struct phy_device *phydev, bool enable, int speed) +{ + bool link_up = false; + int ret = 0; + + if (!phydev->drv) + return -EIO; + + mutex_lock(&phydev->lock); + + if (enable && phydev->loopback_enabled) { + ret = -EBUSY; + goto out; + } + + if (!enable && !phydev->loopback_enabled) { + ret = -EINVAL; + goto out; + } + + if (enable) { + /* + * Link up is signaled with a defined speed. If speed changes, + * then first link down and after that link up needs to be + * signaled. + */ + if (phydev->link && phydev->state == PHY_RUNNING) { + /* link is up and signaled */ + if (speed && phydev->speed != speed) { + /* signal link down and up for new speed */ + phydev->link = false; + phydev->state = PHY_NOLINK; + phy_link_down(phydev); + + link_up = true; + } + } else { + /* link is not signaled */ + if (speed) { + /* signal link up for new speed */ + link_up = true; + } + } + } + + if (phydev->drv->set_loopback) + ret = phydev->drv->set_loopback(phydev, enable, speed); + else + ret = genphy_loopback(phydev, enable, speed); + + if (ret) { + if (enable) { + /* try to restore link if enabling loopback fails */ + if (phydev->drv->set_loopback) + phydev->drv->set_loopback(phydev, false, 0); + else + genphy_loopback(phydev, false, 0); + } + + goto out; + } + + if (link_up) { + phydev->link = true; + phydev->state = PHY_RUNNING; + phy_link_up(phydev); + } + + phydev->loopback_enabled = enable; + +out: + mutex_unlock(&phydev->lock); + return ret; +} +EXPORT_SYMBOL(phy_loopback); + /** * phy_eee_tx_clock_stop_capable() - indicate whether the MAC can stop tx clock * @phydev: target phy_device struct diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 3f734e847e8e2..675fbd2253787 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1818,41 +1818,6 @@ int phy_resume(struct phy_device *phydev) } EXPORT_SYMBOL(phy_resume); -int phy_loopback(struct phy_device *phydev, bool enable) -{ - int ret = 0; - - if (!phydev->drv) - return -EIO; - - mutex_lock(&phydev->lock); - - if (enable && phydev->loopback_enabled) { - ret = -EBUSY; - goto out; - } - - if (!enable && !phydev->loopback_enabled) { - ret = -EINVAL; - goto out; - } - - if (phydev->drv->set_loopback) - ret = phydev->drv->set_loopback(phydev, enable); - else - ret = genphy_loopback(phydev, enable); - - if (ret) - goto out; - - phydev->loopback_enabled = enable; - -out: - mutex_unlock(&phydev->lock); - return ret; -} -EXPORT_SYMBOL(phy_loopback); - /** * phy_reset_after_clk_enable - perform a PHY reset if needed * @phydev: target phy_device struct @@ -2610,12 +2575,18 @@ int genphy_resume(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_resume); -int genphy_loopback(struct phy_device *phydev, bool enable) +int genphy_loopback(struct phy_device *phydev, bool enable, int speed) { if (enable) { u16 ctl = BMCR_LOOPBACK; int ret, val; + if (speed == SPEED_10 || speed == SPEED_100 || + speed == SPEED_1000) + phydev->speed = speed; + else if (speed) + return -EINVAL; + ctl |= mii_bmcr_encode_fixed(phydev->speed, phydev->duplex); phy_modify(phydev, MII_BMCR, ~0, ctl); diff --git a/drivers/net/phy/xilinx_gmii2rgmii.c b/drivers/net/phy/xilinx_gmii2rgmii.c index 7c51daecf18ee..2024d8ef36d98 100644 --- a/drivers/net/phy/xilinx_gmii2rgmii.c +++ b/drivers/net/phy/xilinx_gmii2rgmii.c @@ -64,15 +64,16 @@ static int xgmiitorgmii_read_status(struct phy_device *phydev) return 0; } -static int xgmiitorgmii_set_loopback(struct phy_device *phydev, bool enable) +static int xgmiitorgmii_set_loopback(struct phy_device *phydev, bool enable, + int speed) { struct gmii2rgmii *priv = mdiodev_get_drvdata(&phydev->mdio); int err; if (priv->phy_drv->set_loopback) - err = priv->phy_drv->set_loopback(phydev, enable); + err = priv->phy_drv->set_loopback(phydev, enable, speed); else - err = genphy_loopback(phydev, enable); + err = genphy_loopback(phydev, enable, speed); if (err < 0) return err; diff --git a/include/linux/phy.h b/include/linux/phy.h index c24e1a5658196..60d3b8860ea26 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -1136,8 +1136,16 @@ struct phy_driver { int (*set_tunable)(struct phy_device *dev, struct ethtool_tunable *tuna, const void *data); - /** @set_loopback: Set the loopback mood of the PHY */ - int (*set_loopback)(struct phy_device *dev, bool enable); + /** + * @set_loopback: Set the loopback mode of the PHY + * enable selects if the loopback mode is enabled or disabled. If the + * loopback mode is enabled, then the speed of the loopback mode can be + * requested with the speed argument. If the speed argument is zero, + * then any speed can be selected. If the speed argument is > 0, then + * this speed shall be selected for the loopback mode or EOPNOTSUPP + * shall be returned if speed selection is not supported. + */ + int (*set_loopback)(struct phy_device *dev, bool enable, int speed); /** @get_sqi: Get the signal quality indication */ int (*get_sqi)(struct phy_device *dev); /** @get_sqi_max: Get the maximum signal quality indication */ @@ -1800,7 +1808,7 @@ int phy_init_hw(struct phy_device *phydev); int phy_suspend(struct phy_device *phydev); int phy_resume(struct phy_device *phydev); int __phy_resume(struct phy_device *phydev); -int phy_loopback(struct phy_device *phydev, bool enable); +int phy_loopback(struct phy_device *phydev, bool enable, int speed); int phy_sfp_connect_phy(void *upstream, struct phy_device *phy); void phy_sfp_disconnect_phy(void *upstream, struct phy_device *phy); void phy_sfp_attach(void *upstream, struct sfp_bus *bus); @@ -1915,7 +1923,7 @@ int genphy_read_status(struct phy_device *phydev); int genphy_read_master_slave(struct phy_device *phydev); int genphy_suspend(struct phy_device *phydev); int genphy_resume(struct phy_device *phydev); -int genphy_loopback(struct phy_device *phydev, bool enable); +int genphy_loopback(struct phy_device *phydev, bool enable, int speed); int genphy_soft_reset(struct phy_device *phydev); irqreturn_t genphy_handle_interrupt_no_ack(struct phy_device *phydev); @@ -1957,7 +1965,7 @@ int genphy_c45_pma_baset1_read_master_slave(struct phy_device *phydev); int genphy_c45_read_status(struct phy_device *phydev); int genphy_c45_baset1_read_status(struct phy_device *phydev); int genphy_c45_config_aneg(struct phy_device *phydev); -int genphy_c45_loopback(struct phy_device *phydev, bool enable); +int genphy_c45_loopback(struct phy_device *phydev, bool enable, int speed); int genphy_c45_pma_resume(struct phy_device *phydev); int genphy_c45_pma_suspend(struct phy_device *phydev); int genphy_c45_fast_retrain(struct phy_device *phydev, bool enable); diff --git a/net/core/selftests.c b/net/core/selftests.c index 8f801e6e3b91b..e99ae983fca9b 100644 --- a/net/core/selftests.c +++ b/net/core/selftests.c @@ -299,7 +299,7 @@ static int net_test_phy_loopback_enable(struct net_device *ndev) if (!ndev->phydev) return -EOPNOTSUPP; - return phy_loopback(ndev->phydev, true); + return phy_loopback(ndev->phydev, true, 0); } static int net_test_phy_loopback_disable(struct net_device *ndev) @@ -307,7 +307,7 @@ static int net_test_phy_loopback_disable(struct net_device *ndev) if (!ndev->phydev) return -EOPNOTSUPP; - return phy_loopback(ndev->phydev, false); + return phy_loopback(ndev->phydev, false, 0); } static int net_test_phy_loopback_udp(struct net_device *ndev)