]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: phy: Allow loopback speed selection for PHY drivers
authorGerhard Engleder <gerhard@engleder-embedded.com>
Wed, 12 Mar 2025 20:30:06 +0000 (21:30 +0100)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 20 Mar 2025 07:45:08 +0000 (08:45 +0100)
PHY drivers support loopback mode, but it is not possible to select the
speed of the loopback mode. The speed is chosen by the set_loopback()
operation of the PHY driver. Same is valid for genphy_loopback().

There are PHYs that support loopback with different speeds. Extend
set_loopback() to make loopback speed selection possible.

Signed-off-by: Gerhard Engleder <gerhard@engleder-embedded.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/20250312203010.47429-2-gerhard@engleder-embedded.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/net/phy/adin1100.c
drivers/net/phy/dp83867.c
drivers/net/phy/marvell.c
drivers/net/phy/mxl-gpy.c
drivers/net/phy/phy-c45.c
drivers/net/phy/phy_device.c
drivers/net/phy/xilinx_gmii2rgmii.c
include/linux/phy.h

index 6bb469429b9d051f8f4f0d959e2cd61947eb2826..bd7a47a903acae56c759ba0205c7df3083a9a1ac 100644 (file)
@@ -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);
index c1451df430ace1b0db6127e1cd3ef1ab1f115997..063266cafe9c77be1e7bdf3eaca0effa63d023aa 100644 (file)
@@ -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);
 }
index dd254e36ca8ae44c4ebfc143d880b37c976a8e9f..f2ad675537d1e0d2acaab65145aca5658f761479 100644 (file)
@@ -2131,13 +2131,19 @@ 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)
 {
        int err;
 
        if (enable) {
                u16 bmcr_ctl, mscr2_ctl = 0;
 
+               if (speed == SPEED_10 || speed == SPEED_100 ||
+                   speed == SPEED_1000)
+                       phydev->speed = speed;
+               else if (speed)
+                       return -EINVAL;
+
                bmcr_ctl = mii_bmcr_encode_fixed(phydev->speed, phydev->duplex);
 
                err = phy_write(phydev, MII_BMCR, bmcr_ctl);
index 94d9cb727121a3b581be257da23596501645fc83..a6cca8d432536b24a374fa36ed5a17ff3cc90ac2 100644 (file)
@@ -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);
 }
index 0bcbdce3810710530b7b64cde154436e25f18c85..8a0ffc3174ecf0f116c3519a4440abaefbd6ecea 100644 (file)
@@ -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);
index 3f734e847e8e263a76f440bfdce42b575a4cc370..09864c9635ace42e53ff411f37b845d9c7877500 100644 (file)
@@ -1838,9 +1838,9 @@ int phy_loopback(struct phy_device *phydev, bool enable)
        }
 
        if (phydev->drv->set_loopback)
-               ret = phydev->drv->set_loopback(phydev, enable);
+               ret = phydev->drv->set_loopback(phydev, enable, 0);
        else
-               ret = genphy_loopback(phydev, enable);
+               ret = genphy_loopback(phydev, enable, 0);
 
        if (ret)
                goto out;
@@ -2610,12 +2610,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);
index 7c51daecf18ee04ed047eae4da252aa8f8437592..2024d8ef36d98bc66aa910533903af704c70f00b 100644 (file)
@@ -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;
 
index c24e1a565819629ffa70e2611fa8747debcbd8c9..1c05158e94388514628e3cd39002e329a465d6c0 100644 (file)
@@ -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 */
@@ -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);