#include <linux/interrupt.h>
 #include <linux/delay.h>
 #include <linux/netdevice.h>
+#include <linux/netlink.h>
 #include <linux/etherdevice.h>
 #include <linux/skbuff.h>
 #include <linux/mm.h>
        PHY_STATE_STR(UP)
        PHY_STATE_STR(RUNNING)
        PHY_STATE_STR(NOLINK)
+       PHY_STATE_STR(CABLETEST)
        PHY_STATE_STR(HALTED)
        }
 
        phy_queue_state_machine(phydev, 0);
 }
 
+static void phy_abort_cable_test(struct phy_device *phydev)
+{
+       int err;
+
+       err = phy_init_hw(phydev);
+       if (err)
+               phydev_err(phydev, "Error while aborting cable test");
+}
+
+int phy_start_cable_test(struct phy_device *phydev,
+                        struct netlink_ext_ack *extack)
+{
+       int err;
+
+       if (!(phydev->drv &&
+             phydev->drv->cable_test_start &&
+             phydev->drv->cable_test_get_status)) {
+               NL_SET_ERR_MSG(extack,
+                              "PHY driver does not support cable testing");
+               return -EOPNOTSUPP;
+       }
+
+       mutex_lock(&phydev->lock);
+       if (phydev->state == PHY_CABLETEST) {
+               NL_SET_ERR_MSG(extack,
+                              "PHY already performing a test");
+               err = -EBUSY;
+               goto out;
+       }
+
+       if (phydev->state < PHY_UP ||
+           phydev->state > PHY_CABLETEST) {
+               NL_SET_ERR_MSG(extack,
+                              "PHY not configured. Try setting interface up");
+               err = -EBUSY;
+               goto out;
+       }
+
+       /* Mark the carrier down until the test is complete */
+       phy_link_down(phydev, true);
+
+       err = phydev->drv->cable_test_start(phydev);
+       if (err) {
+               phy_link_up(phydev);
+               goto out;
+       }
+
+       phydev->state = PHY_CABLETEST;
+
+out:
+       mutex_unlock(&phydev->lock);
+
+       return err;
+}
+EXPORT_SYMBOL(phy_start_cable_test);
+
 static int phy_config_aneg(struct phy_device *phydev)
 {
        if (phydev->drv->config_aneg)
 
        mutex_lock(&phydev->lock);
 
+       if (phydev->state == PHY_CABLETEST)
+               phy_abort_cable_test(phydev);
+
        if (phydev->sfp_bus)
                sfp_upstream_stop(phydev->sfp_bus);
 
                        container_of(dwork, struct phy_device, state_queue);
        bool needs_aneg = false, do_suspend = false;
        enum phy_state old_state;
+       bool finished = false;
        int err = 0;
 
        mutex_lock(&phydev->lock);
        case PHY_RUNNING:
                err = phy_check_link_status(phydev);
                break;
+       case PHY_CABLETEST:
+               err = phydev->drv->cable_test_get_status(phydev, &finished);
+               if (err) {
+                       phy_abort_cable_test(phydev);
+                       needs_aneg = true;
+                       phydev->state = PHY_UP;
+                       break;
+               }
+
+               if (finished) {
+                       needs_aneg = true;
+                       phydev->state = PHY_UP;
+               }
+               break;
        case PHY_HALTED:
                if (phydev->link) {
                        phydev->link = 0;
 
 #include <linux/spinlock.h>
 #include <linux/ethtool.h>
 #include <linux/linkmode.h>
+#include <linux/netlink.h>
 #include <linux/mdio.h>
 #include <linux/mii.h>
 #include <linux/mii_timestamper.h>
  * - irq or timer will set NOLINK if link goes down
  * - phy_stop moves to HALTED
  *
+ * CABLETEST: PHY is performing a cable test. Packet reception/sending
+ * is not expected to work, carrier will be indicated as down. PHY will be
+ * poll once per second, or on interrupt for it current state.
+ * Once complete, move to UP to restart the PHY.
+ * - phy_stop aborts the running test and moves to HALTED
+ *
  * HALTED: PHY is up, but no polling or interrupts are done. Or
  * PHY is in an error state.
  * - phy_start moves to UP
        PHY_UP,
        PHY_RUNNING,
        PHY_NOLINK,
+       PHY_CABLETEST,
 };
 
 /**
        int (*module_eeprom)(struct phy_device *dev,
                             struct ethtool_eeprom *ee, u8 *data);
 
+       /* Start a cable test */
+       int (*cable_test_start)(struct phy_device *dev);
+       /* Once per second, or on interrupt, request the status of the
+        * test.
+        */
+       int (*cable_test_get_status)(struct phy_device *dev, bool *finished);
+
        /* Get statistics from the phy using ethtool */
        int (*get_sset_count)(struct phy_device *dev);
        void (*get_strings)(struct phy_device *dev, u8 *data);
 int phy_restart_aneg(struct phy_device *phydev);
 int phy_reset_after_clk_enable(struct phy_device *phydev);
 
+#if IS_ENABLED(CONFIG_PHYLIB)
+int phy_start_cable_test(struct phy_device *phydev,
+                        struct netlink_ext_ack *extack);
+#else
+static inline
+int phy_start_cable_test(struct phy_device *phydev,
+                        struct netlink_ext_ack *extack)
+{
+       NL_SET_ERR_MSG(extack, "Kernel not compiled with PHYLIB support");
+       return -EOPNOTSUPP;
+}
+#endif
+
 static inline void phy_device_reset(struct phy_device *phydev, int value)
 {
        mdio_device_reset(&phydev->mdio, value);