net: phy: marvell: Add cable test support
authorAndrew Lunn <andrew@lunn.ch>
Sun, 10 May 2020 19:12:38 +0000 (21:12 +0200)
committerJakub Kicinski <kuba@kernel.org>
Sun, 10 May 2020 19:28:41 +0000 (12:28 -0700)
The Marvell PHYs have a couple of different register sets for
performing cable tests. Page 7 provides the simplest to use.

v3:
s/mavell/marvell/g
Remove include of <uapi/linux/ethtool_netlink.h>

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/phy/marvell.c

index 7fc8e10c5f33729964f167b178bbf95618b2f43f..4bc7febf9248a387aa2b0ec9fbdd26c2ea731497 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/module.h>
 #include <linux/mii.h>
 #include <linux/ethtool.h>
+#include <linux/ethtool_netlink.h>
 #include <linux/phy.h>
 #include <linux/marvell_phy.h>
 #include <linux/bitfield.h>
@@ -42,6 +43,7 @@
 #define MII_MARVELL_MSCR_PAGE          0x02
 #define MII_MARVELL_LED_PAGE           0x03
 #define MII_MARVELL_MISC_TEST_PAGE     0x06
+#define MII_MARVELL_VCT7_PAGE          0x07
 #define MII_MARVELL_WOL_PAGE           0x11
 
 #define MII_M1011_IEVENT               0x13
 #define MII_88E1510_GEN_CTRL_REG_1_MODE_SGMII  0x1     /* SGMII to copper */
 #define MII_88E1510_GEN_CTRL_REG_1_RESET       0x8000  /* Soft reset */
 
+#define MII_VCT7_PAIR_0_DISTANCE       0x10
+#define MII_VCT7_PAIR_1_DISTANCE       0x11
+#define MII_VCT7_PAIR_2_DISTANCE       0x12
+#define MII_VCT7_PAIR_3_DISTANCE       0x13
+
+#define MII_VCT7_RESULTS       0x14
+#define MII_VCT7_RESULTS_PAIR3_MASK    0xf000
+#define MII_VCT7_RESULTS_PAIR2_MASK    0x0f00
+#define MII_VCT7_RESULTS_PAIR1_MASK    0x00f0
+#define MII_VCT7_RESULTS_PAIR0_MASK    0x000f
+#define MII_VCT7_RESULTS_PAIR3_SHIFT   12
+#define MII_VCT7_RESULTS_PAIR2_SHIFT   8
+#define MII_VCT7_RESULTS_PAIR1_SHIFT   4
+#define MII_VCT7_RESULTS_PAIR0_SHIFT   0
+#define MII_VCT7_RESULTS_INVALID       0
+#define MII_VCT7_RESULTS_OK            1
+#define MII_VCT7_RESULTS_OPEN          2
+#define MII_VCT7_RESULTS_SAME_SHORT    3
+#define MII_VCT7_RESULTS_CROSS_SHORT   4
+#define MII_VCT7_RESULTS_BUSY          9
+
+#define MII_VCT7_CTRL          0x15
+#define MII_VCT7_CTRL_RUN_NOW                  BIT(15)
+#define MII_VCT7_CTRL_RUN_ANEG                 BIT(14)
+#define MII_VCT7_CTRL_DISABLE_CROSS            BIT(13)
+#define MII_VCT7_CTRL_RUN_AFTER_BREAK_LINK     BIT(12)
+#define MII_VCT7_CTRL_IN_PROGRESS              BIT(11)
+#define MII_VCT7_CTRL_METERS                   BIT(10)
+#define MII_VCT7_CTRL_CENTIMETERS              0
+
 #define LPA_PAUSE_FIBER                0x180
 #define LPA_PAUSE_ASYM_FIBER   0x100
 
@@ -1658,6 +1690,163 @@ static void marvell_get_stats(struct phy_device *phydev,
                data[i] = marvell_get_stat(phydev, i);
 }
 
+static int marvell_vct7_cable_test_start(struct phy_device *phydev)
+{
+       int bmcr, bmsr, ret;
+
+       /* If auto-negotiation is enabled, but not complete, the cable
+        * test never completes. So disable auto-neg.
+        */
+       bmcr = phy_read(phydev, MII_BMCR);
+       if (bmcr < 0)
+               return bmcr;
+
+       bmsr = phy_read(phydev, MII_BMSR);
+
+       if (bmsr < 0)
+               return bmsr;
+
+       if (bmcr & BMCR_ANENABLE) {
+               ret =  phy_modify(phydev, MII_BMCR, BMCR_ANENABLE, 0);
+               if (ret < 0)
+                       return ret;
+               ret = genphy_soft_reset(phydev);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /* If the link is up, allow it some time to go down */
+       if (bmsr & BMSR_LSTATUS)
+               msleep(1500);
+
+       return phy_write_paged(phydev, MII_MARVELL_VCT7_PAGE,
+                              MII_VCT7_CTRL,
+                              MII_VCT7_CTRL_RUN_NOW |
+                              MII_VCT7_CTRL_CENTIMETERS);
+}
+
+static int marvell_vct7_distance_to_length(int distance, bool meter)
+{
+       if (meter)
+               distance *= 100;
+
+       return distance;
+}
+
+static bool marvell_vct7_distance_valid(int result)
+{
+       switch (result) {
+       case MII_VCT7_RESULTS_OPEN:
+       case MII_VCT7_RESULTS_SAME_SHORT:
+       case MII_VCT7_RESULTS_CROSS_SHORT:
+               return true;
+       }
+       return false;
+}
+
+static int marvell_vct7_report_length(struct phy_device *phydev,
+                                     int pair, bool meter)
+{
+       int length;
+       int ret;
+
+       ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
+                            MII_VCT7_PAIR_0_DISTANCE + pair);
+       if (ret < 0)
+               return ret;
+
+       length = marvell_vct7_distance_to_length(ret, meter);
+
+       ethnl_cable_test_fault_length(phydev, pair, length);
+
+       return 0;
+}
+
+static int marvell_vct7_cable_test_report_trans(int result)
+{
+       switch (result) {
+       case MII_VCT7_RESULTS_OK:
+               return ETHTOOL_A_CABLE_RESULT_CODE_OK;
+       case MII_VCT7_RESULTS_OPEN:
+               return ETHTOOL_A_CABLE_RESULT_CODE_OPEN;
+       case MII_VCT7_RESULTS_SAME_SHORT:
+               return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT;
+       case MII_VCT7_RESULTS_CROSS_SHORT:
+               return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT;
+       default:
+               return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC;
+       }
+}
+
+static int marvell_vct7_cable_test_report(struct phy_device *phydev)
+{
+       int pair0, pair1, pair2, pair3;
+       bool meter;
+       int ret;
+
+       ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
+                            MII_VCT7_RESULTS);
+       if (ret < 0)
+               return ret;
+
+       pair3 = (ret & MII_VCT7_RESULTS_PAIR3_MASK) >>
+               MII_VCT7_RESULTS_PAIR3_SHIFT;
+       pair2 = (ret & MII_VCT7_RESULTS_PAIR2_MASK) >>
+               MII_VCT7_RESULTS_PAIR2_SHIFT;
+       pair1 = (ret & MII_VCT7_RESULTS_PAIR1_MASK) >>
+               MII_VCT7_RESULTS_PAIR1_SHIFT;
+       pair0 = (ret & MII_VCT7_RESULTS_PAIR0_MASK) >>
+               MII_VCT7_RESULTS_PAIR0_SHIFT;
+
+       ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A,
+                               marvell_vct7_cable_test_report_trans(pair0));
+       ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B,
+                               marvell_vct7_cable_test_report_trans(pair1));
+       ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C,
+                               marvell_vct7_cable_test_report_trans(pair2));
+       ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D,
+                               marvell_vct7_cable_test_report_trans(pair3));
+
+       ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE, MII_VCT7_CTRL);
+       if (ret < 0)
+               return ret;
+
+       meter = ret & MII_VCT7_CTRL_METERS;
+
+       if (marvell_vct7_distance_valid(pair0))
+               marvell_vct7_report_length(phydev, 0, meter);
+       if (marvell_vct7_distance_valid(pair1))
+               marvell_vct7_report_length(phydev, 1, meter);
+       if (marvell_vct7_distance_valid(pair2))
+               marvell_vct7_report_length(phydev, 2, meter);
+       if (marvell_vct7_distance_valid(pair3))
+               marvell_vct7_report_length(phydev, 3, meter);
+
+       return 0;
+}
+
+static int marvell_vct7_cable_test_get_status(struct phy_device *phydev,
+                                             bool *finished)
+{
+       int ret;
+
+       *finished = false;
+
+       ret = phy_read_paged(phydev, MII_MARVELL_VCT7_PAGE,
+                            MII_VCT7_CTRL);
+
+       if (ret < 0)
+               return ret;
+
+       if (!(ret & MII_VCT7_CTRL_IN_PROGRESS)) {
+               *finished = true;
+
+               return marvell_vct7_cable_test_report(phydev);
+       }
+
+       return 0;
+}
+
 #ifdef CONFIG_HWMON
 static int m88e1121_get_temp(struct phy_device *phydev, long *temp)
 {
@@ -2353,6 +2542,7 @@ static struct phy_driver marvell_drivers[] = {
                .phy_id_mask = MARVELL_PHY_ID_MASK,
                .name = "Marvell 88E1510",
                .features = PHY_GBIT_FIBRE_FEATURES,
+               .flags = PHY_POLL_CABLE_TEST,
                .probe = &m88e1510_probe,
                .config_init = &m88e1510_config_init,
                .config_aneg = &m88e1510_config_aneg,
@@ -2372,12 +2562,15 @@ static struct phy_driver marvell_drivers[] = {
                .set_loopback = genphy_loopback,
                .get_tunable = m88e1011_get_tunable,
                .set_tunable = m88e1011_set_tunable,
+               .cable_test_start = marvell_vct7_cable_test_start,
+               .cable_test_get_status = marvell_vct7_cable_test_get_status,
        },
        {
                .phy_id = MARVELL_PHY_ID_88E1540,
                .phy_id_mask = MARVELL_PHY_ID_MASK,
                .name = "Marvell 88E1540",
                /* PHY_GBIT_FEATURES */
+               .flags = PHY_POLL_CABLE_TEST,
                .probe = m88e1510_probe,
                .config_init = &marvell_config_init,
                .config_aneg = &m88e1510_config_aneg,
@@ -2394,6 +2587,8 @@ static struct phy_driver marvell_drivers[] = {
                .get_stats = marvell_get_stats,
                .get_tunable = m88e1540_get_tunable,
                .set_tunable = m88e1540_set_tunable,
+               .cable_test_start = marvell_vct7_cable_test_start,
+               .cable_test_get_status = marvell_vct7_cable_test_get_status,
        },
        {
                .phy_id = MARVELL_PHY_ID_88E1545,
@@ -2401,6 +2596,7 @@ static struct phy_driver marvell_drivers[] = {
                .name = "Marvell 88E1545",
                .probe = m88e1510_probe,
                /* PHY_GBIT_FEATURES */
+               .flags = PHY_POLL_CABLE_TEST,
                .config_init = &marvell_config_init,
                .config_aneg = &m88e1510_config_aneg,
                .read_status = &marvell_read_status,
@@ -2416,6 +2612,8 @@ static struct phy_driver marvell_drivers[] = {
                .get_stats = marvell_get_stats,
                .get_tunable = m88e1540_get_tunable,
                .set_tunable = m88e1540_set_tunable,
+               .cable_test_start = marvell_vct7_cable_test_start,
+               .cable_test_get_status = marvell_vct7_cable_test_get_status,
        },
        {
                .phy_id = MARVELL_PHY_ID_88E3016,
@@ -2442,6 +2640,7 @@ static struct phy_driver marvell_drivers[] = {
                .phy_id_mask = MARVELL_PHY_ID_MASK,
                .name = "Marvell 88E6390",
                /* PHY_GBIT_FEATURES */
+               .flags = PHY_POLL_CABLE_TEST,
                .probe = m88e6390_probe,
                .config_init = &marvell_config_init,
                .config_aneg = &m88e6390_config_aneg,
@@ -2458,6 +2657,8 @@ static struct phy_driver marvell_drivers[] = {
                .get_stats = marvell_get_stats,
                .get_tunable = m88e1540_get_tunable,
                .set_tunable = m88e1540_set_tunable,
+               .cable_test_start = marvell_vct7_cable_test_start,
+               .cable_test_get_status = marvell_vct7_cable_test_get_status,
        },
 };