net: phy: micrel: ksz886x/ksz8081: add cabletest support
authorOleksij Rempel <o.rempel@pengutronix.de>
Mon, 14 Jun 2021 04:31:25 +0000 (06:31 +0200)
committerDavid S. Miller <davem@davemloft.net>
Mon, 14 Jun 2021 19:54:43 +0000 (12:54 -0700)
This patch support for cable test for the ksz886x switches and the
ksz8081 PHY.

The patch was tested on a KSZ8873RLL switch with following results:

- port 1:
  - provides invalid values, thus return -ENOTSUPP
    (Errata: DS80000830A: "LinkMD does not work on Port 1",
     http://ww1.microchip.com/downloads/en/DeviceDoc/KSZ8873-Errata-DS80000830A.pdf)

- port 2:
  - can detect distance
  - can detect open on each wire of pair A (wire 1 and 2)
  - can detect open only on one wire of pair B (only wire 3)
  - can detect short between wires of a pair (wires 1 + 2 or 3 + 6)
  - short between pairs is detected as open.
    For example short between wires 2 + 3 is detected as open.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/dsa/microchip/ksz8795.c
drivers/net/phy/micrel.c
include/linux/micrel_phy.h

index e1731ae..560f684 100644 (file)
@@ -970,6 +970,18 @@ static enum dsa_tag_protocol ksz8_get_tag_protocol(struct dsa_switch *ds,
                DSA_TAG_PROTO_KSZ9893 : DSA_TAG_PROTO_KSZ8795;
 }
 
+static u32 ksz8_sw_get_phy_flags(struct dsa_switch *ds, int port)
+{
+       /* Silicon Errata Sheet (DS80000830A):
+        * Port 1 does not work with LinkMD Cable-Testing.
+        * Port 1 does not respond to received PAUSE control frames.
+        */
+       if (!port)
+               return MICREL_KSZ8_P1_ERRATA;
+
+       return 0;
+}
+
 static void ksz8_get_strings(struct dsa_switch *ds, int port,
                             u32 stringset, uint8_t *buf)
 {
@@ -1503,6 +1515,7 @@ unsupported:
 
 static const struct dsa_switch_ops ksz8_switch_ops = {
        .get_tag_protocol       = ksz8_get_tag_protocol,
+       .get_phy_flags          = ksz8_sw_get_phy_flags,
        .setup                  = ksz8_setup,
        .phy_read               = ksz_phy_read16,
        .phy_write              = ksz_phy_write16,
index 5ca39ce..4d53886 100644 (file)
@@ -20,6 +20,7 @@
  */
 
 #include <linux/bitfield.h>
+#include <linux/ethtool_netlink.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/phy.h>
 #define KSZPHY_INTCS_STATUS                    (KSZPHY_INTCS_LINK_DOWN_STATUS |\
                                                 KSZPHY_INTCS_LINK_UP_STATUS)
 
+/* LinkMD Control/Status */
+#define KSZ8081_LMD                            0x1d
+#define KSZ8081_LMD_ENABLE_TEST                        BIT(15)
+#define KSZ8081_LMD_STAT_NORMAL                        0
+#define KSZ8081_LMD_STAT_OPEN                  1
+#define KSZ8081_LMD_STAT_SHORT                 2
+#define KSZ8081_LMD_STAT_FAIL                  3
+#define KSZ8081_LMD_STAT_MASK                  GENMASK(14, 13)
+/* Short cable (<10 meter) has been detected by LinkMD */
+#define KSZ8081_LMD_SHORT_INDICATOR            BIT(12)
+#define KSZ8081_LMD_DELTA_TIME_MASK            GENMASK(8, 0)
+
 /* PHY Control 1 */
 #define MII_KSZPHY_CTRL_1                      0x1e
 #define KSZ8081_CTRL1_MDIX_STAT                        BIT(4)
@@ -1363,6 +1376,167 @@ static int kszphy_probe(struct phy_device *phydev)
        return 0;
 }
 
+static int ksz886x_cable_test_start(struct phy_device *phydev)
+{
+       if (phydev->dev_flags & MICREL_KSZ8_P1_ERRATA)
+               return -EOPNOTSUPP;
+
+       /* If autoneg is enabled, we won't be able to test cross pair
+        * short. In this case, the PHY will "detect" a link and
+        * confuse the internal state machine - disable auto neg here.
+        * If autoneg is disabled, we should set the speed to 10mbit.
+        */
+       return phy_clear_bits(phydev, MII_BMCR, BMCR_ANENABLE | BMCR_SPEED100);
+}
+
+static int ksz886x_cable_test_result_trans(u16 status)
+{
+       switch (FIELD_GET(KSZ8081_LMD_STAT_MASK, status)) {
+       case KSZ8081_LMD_STAT_NORMAL:
+               return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+       case KSZ8081_LMD_STAT_SHORT:
+               return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+       case KSZ8081_LMD_STAT_OPEN:
+               return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+       case KSZ8081_LMD_STAT_FAIL:
+               fallthrough;
+       default:
+               return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+       }
+}
+
+static bool ksz886x_cable_test_failed(u16 status)
+{
+       return FIELD_GET(KSZ8081_LMD_STAT_MASK, status) ==
+               KSZ8081_LMD_STAT_FAIL;
+}
+
+static bool ksz886x_cable_test_fault_length_valid(u16 status)
+{
+       switch (FIELD_GET(KSZ8081_LMD_STAT_MASK, status)) {
+       case KSZ8081_LMD_STAT_OPEN:
+               fallthrough;
+       case KSZ8081_LMD_STAT_SHORT:
+               return true;
+       }
+       return false;
+}
+
+static int ksz886x_cable_test_fault_length(u16 status)
+{
+       int dt;
+
+       /* According to the data sheet the distance to the fault is
+        * DELTA_TIME * 0.4 meters.
+        */
+       dt = FIELD_GET(KSZ8081_LMD_DELTA_TIME_MASK, status);
+
+       return (dt * 400) / 10;
+}
+
+static int ksz886x_cable_test_wait_for_completion(struct phy_device *phydev)
+{
+       int val, ret;
+
+       ret = phy_read_poll_timeout(phydev, KSZ8081_LMD, val,
+                                   !(val & KSZ8081_LMD_ENABLE_TEST),
+                                   30000, 100000, true);
+
+       return ret < 0 ? ret : 0;
+}
+
+static int ksz886x_cable_test_one_pair(struct phy_device *phydev, int pair)
+{
+       static const int ethtool_pair[] = {
+               ETHTOOL_A_CABLE_PAIR_A,
+               ETHTOOL_A_CABLE_PAIR_B,
+       };
+       int ret, val, mdix;
+
+       /* There is no way to choice the pair, like we do one ksz9031.
+        * We can workaround this limitation by using the MDI-X functionality.
+        */
+       if (pair == 0)
+               mdix = ETH_TP_MDI;
+       else
+               mdix = ETH_TP_MDI_X;
+
+       switch (phydev->phy_id & MICREL_PHY_ID_MASK) {
+       case PHY_ID_KSZ8081:
+               ret = ksz8081_config_mdix(phydev, mdix);
+               break;
+       case PHY_ID_KSZ886X:
+               ret = ksz886x_config_mdix(phydev, mdix);
+               break;
+       default:
+               ret = -ENODEV;
+       }
+
+       if (ret)
+               return ret;
+
+       /* Now we are ready to fire. This command will send a 100ns pulse
+        * to the pair.
+        */
+       ret = phy_write(phydev, KSZ8081_LMD, KSZ8081_LMD_ENABLE_TEST);
+       if (ret)
+               return ret;
+
+       ret = ksz886x_cable_test_wait_for_completion(phydev);
+       if (ret)
+               return ret;
+
+       val = phy_read(phydev, KSZ8081_LMD);
+       if (val < 0)
+               return val;
+
+       if (ksz886x_cable_test_failed(val))
+               return -EAGAIN;
+
+       ret = ethnl_cable_test_result(phydev, ethtool_pair[pair],
+                                     ksz886x_cable_test_result_trans(val));
+       if (ret)
+               return ret;
+
+       if (!ksz886x_cable_test_fault_length_valid(val))
+               return 0;
+
+       return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
+                                            ksz886x_cable_test_fault_length(val));
+}
+
+static int ksz886x_cable_test_get_status(struct phy_device *phydev,
+                                        bool *finished)
+{
+       unsigned long pair_mask = 0x3;
+       int retries = 20;
+       int pair, ret;
+
+       *finished = false;
+
+       /* Try harder if link partner is active */
+       while (pair_mask && retries--) {
+               for_each_set_bit(pair, &pair_mask, 4) {
+                       ret = ksz886x_cable_test_one_pair(phydev, pair);
+                       if (ret == -EAGAIN)
+                               continue;
+                       if (ret < 0)
+                               return ret;
+                       clear_bit(pair, &pair_mask);
+               }
+               /* If link partner is in autonegotiation mode it will send 2ms
+                * of FLPs with at least 6ms of silence.
+                * Add 2ms sleep to have better chances to hit this silence.
+                */
+               if (pair_mask)
+                       msleep(2);
+       }
+
+       *finished = true;
+
+       return ret;
+}
+
 static struct phy_driver ksphy_driver[] = {
 {
        .phy_id         = PHY_ID_KS8737,
@@ -1469,6 +1643,7 @@ static struct phy_driver ksphy_driver[] = {
        .phy_id         = PHY_ID_KSZ8081,
        .name           = "Micrel KSZ8081 or KSZ8091",
        .phy_id_mask    = MICREL_PHY_ID_MASK,
+       .flags          = PHY_POLL_CABLE_TEST,
        /* PHY_BASIC_FEATURES */
        .driver_data    = &ksz8081_type,
        .probe          = kszphy_probe,
@@ -1483,6 +1658,8 @@ static struct phy_driver ksphy_driver[] = {
        .get_stats      = kszphy_get_stats,
        .suspend        = kszphy_suspend,
        .resume         = kszphy_resume,
+       .cable_test_start       = ksz886x_cable_test_start,
+       .cable_test_get_status  = ksz886x_cable_test_get_status,
 }, {
        .phy_id         = PHY_ID_KSZ8061,
        .name           = "Micrel KSZ8061",
@@ -1571,11 +1748,14 @@ static struct phy_driver ksphy_driver[] = {
        .phy_id_mask    = MICREL_PHY_ID_MASK,
        .name           = "Micrel KSZ8851 Ethernet MAC or KSZ886X Switch",
        /* PHY_BASIC_FEATURES */
+       .flags          = PHY_POLL_CABLE_TEST,
        .config_init    = kszphy_config_init,
        .config_aneg    = ksz886x_config_aneg,
        .read_status    = ksz886x_read_status,
        .suspend        = genphy_suspend,
        .resume         = genphy_resume,
+       .cable_test_start       = ksz886x_cable_test_start,
+       .cable_test_get_status  = ksz886x_cable_test_get_status,
 }, {
        .name           = "Micrel KSZ87XX Switch",
        /* PHY_BASIC_FEATURES */
index 58370ab..3d43c60 100644 (file)
@@ -39,6 +39,7 @@
 /* struct phy_device dev_flags definitions */
 #define MICREL_PHY_50MHZ_CLK   0x00000001
 #define MICREL_PHY_FXEN                0x00000002
+#define MICREL_KSZ8_P1_ERRATA  0x00000003
 
 #define MICREL_KSZ9021_EXTREG_CTRL     0xB
 #define MICREL_KSZ9021_EXTREG_DATA_WRITE       0xC