net: phy: broadcom: add cable test support
authorMichael Walle <michael@walle.cc>
Wed, 13 May 2020 16:35:23 +0000 (18:35 +0200)
committerDavid S. Miller <davem@davemloft.net>
Wed, 13 May 2020 19:52:38 +0000 (12:52 -0700)
Most modern broadcom PHYs support ECD (enhanced cable diagnostics). Add
support for it in the bcm-phy-lib so they can easily be used in the PHY
driver.

There are two access methods for ECD: legacy by expansion registers and
via the new RDB registers which are exclusive. Provide functions in two
variants where the PHY driver can choose from. To keep things simple for
now, we just switch the register access to expansion registers in the
RDB variant for now. On the flipside, we have to keep a bus lock to
prevent any other non-legacy access on the PHY.

The results of the intra-pair tests are inconclusive (at least for the
BCM54140). Most of the times half the length is reported but sometimes
the length is correct.

Signed-off-by: Michael Walle <michael@walle.cc>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/bcm-phy-lib.c
drivers/net/phy/bcm-phy-lib.h
include/linux/brcmphy.h

index 41c728fbcfb264d70d28684809f66a760e85a9f1..cb92786e3dedcc5420649b9ed73173ac5c0957e0 100644 (file)
@@ -4,12 +4,14 @@
  */
 
 #include "bcm-phy-lib.h"
+#include <linux/bitfield.h>
 #include <linux/brcmphy.h>
 #include <linux/export.h>
 #include <linux/mdio.h>
 #include <linux/module.h>
 #include <linux/phy.h>
 #include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
 
 #define MII_BCM_CHANNEL_WIDTH     0x2000
 #define BCM_CL45VEN_EEE_ADV       0x3c
@@ -581,6 +583,193 @@ int bcm_phy_enable_jumbo(struct phy_device *phydev)
 }
 EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo);
 
+int __bcm_phy_enable_rdb_access(struct phy_device *phydev)
+{
+       return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0);
+}
+EXPORT_SYMBOL_GPL(__bcm_phy_enable_rdb_access);
+
+int __bcm_phy_enable_legacy_access(struct phy_device *phydev)
+{
+       return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087,
+                                  BCM54XX_ACCESS_MODE_LEGACY_EN);
+}
+EXPORT_SYMBOL_GPL(__bcm_phy_enable_legacy_access);
+
+static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb)
+{
+       u16 mask, set;
+       int ret;
+
+       /* Auto-negotiation must be enabled for cable diagnostics to work, but
+        * don't advertise any capabilities.
+        */
+       phy_write(phydev, MII_BMCR, BMCR_ANENABLE);
+       phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA);
+       phy_write(phydev, MII_CTRL1000, 0);
+
+       phy_lock_mdio_bus(phydev);
+       if (is_rdb) {
+               ret = __bcm_phy_enable_legacy_access(phydev);
+               if (ret)
+                       goto out;
+       }
+
+       mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK;
+       set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK |
+             FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK,
+                        BCM54XX_ECD_CTRL_UNIT_CM);
+
+       ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set);
+
+out:
+       /* re-enable the RDB access even if there was an error */
+       if (is_rdb)
+               ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
+
+       phy_unlock_mdio_bus(phydev);
+
+       return ret;
+}
+
+static int bcm_phy_cable_test_report_trans(int result)
+{
+       switch (result) {
+       case BCM54XX_ECD_FAULT_TYPE_OK:
+               return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+       case BCM54XX_ECD_FAULT_TYPE_OPEN:
+               return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+       case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
+               return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+       case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
+               return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+       case BCM54XX_ECD_FAULT_TYPE_INVALID:
+       case BCM54XX_ECD_FAULT_TYPE_BUSY:
+       default:
+               return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+       }
+}
+
+static bool bcm_phy_distance_valid(int result)
+{
+       switch (result) {
+       case BCM54XX_ECD_FAULT_TYPE_OPEN:
+       case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT:
+       case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT:
+               return true;
+       }
+       return false;
+}
+
+static int bcm_phy_report_length(struct phy_device *phydev, int pair)
+{
+       int val;
+
+       val = __bcm_phy_read_exp(phydev,
+                                BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair);
+       if (val < 0)
+               return val;
+
+       if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID)
+               return 0;
+
+       ethnl_cable_test_fault_length(phydev, pair, val);
+
+       return 0;
+}
+
+static int _bcm_phy_cable_test_get_status(struct phy_device *phydev,
+                                         bool *finished, bool is_rdb)
+{
+       int pair_a, pair_b, pair_c, pair_d, ret;
+
+       *finished = false;
+
+       phy_lock_mdio_bus(phydev);
+
+       if (is_rdb) {
+               ret = __bcm_phy_enable_legacy_access(phydev);
+               if (ret)
+                       goto out;
+       }
+
+       ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL);
+       if (ret < 0)
+               goto out;
+
+       if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) {
+               ret = 0;
+               goto out;
+       }
+
+       ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE);
+       if (ret < 0)
+               goto out;
+
+       pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret);
+       pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret);
+       pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret);
+       pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret);
+
+       ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+                               bcm_phy_cable_test_report_trans(pair_a));
+       ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
+                               bcm_phy_cable_test_report_trans(pair_b));
+       ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
+                               bcm_phy_cable_test_report_trans(pair_c));
+       ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+                               bcm_phy_cable_test_report_trans(pair_d));
+
+       if (bcm_phy_distance_valid(pair_a))
+               bcm_phy_report_length(phydev, 0);
+       if (bcm_phy_distance_valid(pair_b))
+               bcm_phy_report_length(phydev, 1);
+       if (bcm_phy_distance_valid(pair_c))
+               bcm_phy_report_length(phydev, 2);
+       if (bcm_phy_distance_valid(pair_d))
+               bcm_phy_report_length(phydev, 3);
+
+       ret = 0;
+       *finished = true;
+out:
+       /* re-enable the RDB access even if there was an error */
+       if (is_rdb)
+               ret = __bcm_phy_enable_rdb_access(phydev) ? : ret;
+
+       phy_unlock_mdio_bus(phydev);
+
+       return ret;
+}
+
+int bcm_phy_cable_test_start(struct phy_device *phydev)
+{
+       return _bcm_phy_cable_test_start(phydev, false);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start);
+
+int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished)
+{
+       return _bcm_phy_cable_test_get_status(phydev, finished, false);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status);
+
+/* We assume that all PHYs which support RDB access can be switched to legacy
+ * mode. If, in the future, this is not true anymore, we have to re-implement
+ * this with RDB access.
+ */
+int bcm_phy_cable_test_start_rdb(struct phy_device *phydev)
+{
+       return _bcm_phy_cable_test_start(phydev, true);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb);
+
+int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
+                                     bool *finished)
+{
+       return _bcm_phy_cable_test_get_status(phydev, finished, true);
+}
+EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb);
+
 MODULE_DESCRIPTION("Broadcom PHY Library");
 MODULE_LICENSE("GPL v2");
 MODULE_AUTHOR("Broadcom Corporation");
index b35d880220b9f2e66de6038a9105bb170e449ba4..237a8503c9b40d6a359c563f9528e2a2a63772f1 100644 (file)
@@ -80,4 +80,10 @@ void bcm_phy_r_rc_cal_reset(struct phy_device *phydev);
 int bcm_phy_28nm_a0b0_afe_config_init(struct phy_device *phydev);
 int bcm_phy_enable_jumbo(struct phy_device *phydev);
 
+int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev,
+                                     bool *finished);
+int bcm_phy_cable_test_start_rdb(struct phy_device *phydev);
+int bcm_phy_cable_test_start(struct phy_device *phydev);
+int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished);
+
 #endif /* _LINUX_BCM_PHY_LIB_H */
index 58d0150acc3edbd88d82ea11b474bfa94eade868..d41624db6de243e713806523d93205f955f37b5c 100644 (file)
 #define MII_BCM54XX_RDB_ADDR   0x1e
 #define MII_BCM54XX_RDB_DATA   0x1f
 
+/* legacy access control via rdb/expansion register */
+#define BCM54XX_RDB_REG0087            0x0087
+#define BCM54XX_EXP_REG7E              (MII_BCM54XX_EXP_SEL_ER + 0x7E)
+#define BCM54XX_ACCESS_MODE_LEGACY_EN  BIT(15)
+
 /*
  * AUXILIARY CONTROL SHADOW ACCESS REGISTERS.  (PHY REG 0x18)
  */
 #define MII_BRCM_CORE_EXPB0    0xB0
 #define MII_BRCM_CORE_EXPB1    0xB1
 
+/* Enhanced Cable Diagnostics */
+#define BCM54XX_RDB_ECD_CTRL                   0x2a0
+#define BCM54XX_EXP_ECD_CTRL                   (MII_BCM54XX_EXP_SEL_ER + 0xc0)
+
+#define BCM54XX_ECD_CTRL_CABLE_TYPE_CAT3       1       /* CAT3 or worse */
+#define BCM54XX_ECD_CTRL_CABLE_TYPE_CAT5       0       /* CAT5 or better */
+#define BCM54XX_ECD_CTRL_CABLE_TYPE_MASK       BIT(0)  /* cable type */
+#define BCM54XX_ECD_CTRL_INVALID               BIT(3)  /* invalid result */
+#define BCM54XX_ECD_CTRL_UNIT_CM               0       /* centimeters */
+#define BCM54XX_ECD_CTRL_UNIT_M                        1       /* meters */
+#define BCM54XX_ECD_CTRL_UNIT_MASK             BIT(10) /* cable length unit */
+#define BCM54XX_ECD_CTRL_IN_PROGRESS           BIT(11) /* test in progress */
+#define BCM54XX_ECD_CTRL_BREAK_LINK            BIT(12) /* unconnect link
+                                                        * during test
+                                                        */
+#define BCM54XX_ECD_CTRL_CROSS_SHORT_DIS       BIT(13) /* disable inter-pair
+                                                        * short check
+                                                        */
+#define BCM54XX_ECD_CTRL_RUN                   BIT(15) /* run immediate */
+
+#define BCM54XX_RDB_ECD_FAULT_TYPE             0x2a1
+#define BCM54XX_EXP_ECD_FAULT_TYPE             (MII_BCM54XX_EXP_SEL_ER + 0xc1)
+#define BCM54XX_ECD_FAULT_TYPE_INVALID         0x0
+#define BCM54XX_ECD_FAULT_TYPE_OK              0x1
+#define BCM54XX_ECD_FAULT_TYPE_OPEN            0x2
+#define BCM54XX_ECD_FAULT_TYPE_SAME_SHORT      0x3 /* short same pair */
+#define BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT     0x4 /* short different pairs */
+#define BCM54XX_ECD_FAULT_TYPE_BUSY            0x9
+#define BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK     GENMASK(3, 0)
+#define BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK     GENMASK(7, 4)
+#define BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK     GENMASK(11, 8)
+#define BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK     GENMASK(15, 12)
+#define BCM54XX_ECD_PAIR_A_LENGTH_RESULTS      0x2a2
+#define BCM54XX_ECD_PAIR_B_LENGTH_RESULTS      0x2a3
+#define BCM54XX_ECD_PAIR_C_LENGTH_RESULTS      0x2a4
+#define BCM54XX_ECD_PAIR_D_LENGTH_RESULTS      0x2a5
+
+#define BCM54XX_RDB_ECD_PAIR_A_LENGTH_RESULTS  0x2a2
+#define BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS  (MII_BCM54XX_EXP_SEL_ER + 0xc2)
+#define BCM54XX_RDB_ECD_PAIR_B_LENGTH_RESULTS  0x2a3
+#define BCM54XX_EXP_ECD_PAIR_B_LENGTH_RESULTS  (MII_BCM54XX_EXP_SEL_ER + 0xc3)
+#define BCM54XX_RDB_ECD_PAIR_C_LENGTH_RESULTS  0x2a4
+#define BCM54XX_EXP_ECD_PAIR_C_LENGTH_RESULTS  (MII_BCM54XX_EXP_SEL_ER + 0xc4)
+#define BCM54XX_RDB_ECD_PAIR_D_LENGTH_RESULTS  0x2a5
+#define BCM54XX_EXP_ECD_PAIR_D_LENGTH_RESULTS  (MII_BCM54XX_EXP_SEL_ER + 0xc5)
+#define BCM54XX_ECD_LENGTH_RESULTS_INVALID     0xffff
+
 #endif /* _LINUX_BRCMPHY_H */