diff options
author | Divya Koppera <Divya.Koppera@microchip.com> | 2022-09-09 11:31:23 +0300 |
---|---|---|
committer | Jakub Kicinski <kuba@kernel.org> | 2022-09-20 03:57:46 +0300 |
commit | 21b688dabecb6a949f998d92579ae1b800a96e7f (patch) | |
tree | dd5743ce178e20875affc4c409494b8ac74df6ea | |
parent | 517ff3ceb38317a8e8a88b3e4ed68b21ceb31d2b (diff) | |
download | linux-21b688dabecb6a949f998d92579ae1b800a96e7f.tar.xz |
net: phy: micrel: Cable Diag feature for lan8814 phy
Support for Cable Diagnostics in lan8814 phy
Signed-off-by: Divya Koppera <Divya.Koppera@microchip.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://lore.kernel.org/r/20220909083123.30134-1-Divya.Koppera@microchip.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r-- | drivers/net/phy/micrel.c | 125 |
1 files changed, 107 insertions, 18 deletions
diff --git a/drivers/net/phy/micrel.c b/drivers/net/phy/micrel.c index 7b8c5c8d013e..491a04b89aa8 100644 --- a/drivers/net/phy/micrel.c +++ b/drivers/net/phy/micrel.c @@ -92,6 +92,15 @@ #define KSZ9x31_LMD_VCT_DATA_HI_PULSE_MASK GENMASK(1, 0) #define KSZ9x31_LMD_VCT_DATA_MASK GENMASK(7, 0) +#define KSZPHY_WIRE_PAIR_MASK 0x3 + +#define LAN8814_CABLE_DIAG 0x12 +#define LAN8814_CABLE_DIAG_STAT_MASK GENMASK(9, 8) +#define LAN8814_CABLE_DIAG_VCT_DATA_MASK GENMASK(7, 0) +#define LAN8814_PAIR_BIT_SHIFT 12 + +#define LAN8814_WIRE_PAIR_MASK 0xF + /* Lan8814 general Interrupt control/status reg in GPHY specific block. */ #define LAN8814_INTC 0x18 #define LAN8814_INTS 0x1B @@ -257,6 +266,8 @@ static struct kszphy_hw_stat kszphy_hw_stats[] = { struct kszphy_type { u32 led_mode_reg; u16 interrupt_level_mask; + u16 cable_diag_reg; + unsigned long pair_mask; bool has_broadcast_disable; bool has_nand_tree_disable; bool has_rmii_ref_clk_sel; @@ -313,6 +324,13 @@ struct kszphy_priv { static const struct kszphy_type lan8814_type = { .led_mode_reg = ~LAN8814_LED_CTRL_1, + .cable_diag_reg = LAN8814_CABLE_DIAG, + .pair_mask = LAN8814_WIRE_PAIR_MASK, +}; + +static const struct kszphy_type ksz886x_type = { + .cable_diag_reg = KSZ8081_LMD, + .pair_mask = KSZPHY_WIRE_PAIR_MASK, }; static const struct kszphy_type ksz8021_type = { @@ -1796,6 +1814,17 @@ static int kszphy_probe(struct phy_device *phydev) return 0; } +static int lan8814_cable_test_start(struct phy_device *phydev) +{ + /* 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. + * Set the speed to 1000mbit and full duplex. + */ + return phy_modify(phydev, MII_BMCR, BMCR_ANENABLE | BMCR_SPEED100, + BMCR_SPEED1000 | BMCR_FULLDPLX); +} + static int ksz886x_cable_test_start(struct phy_device *phydev) { if (phydev->dev_flags & MICREL_KSZ8_P1_ERRATA) @@ -1809,9 +1838,9 @@ static int ksz886x_cable_test_start(struct phy_device *phydev) return phy_clear_bits(phydev, MII_BMCR, BMCR_ANENABLE | BMCR_SPEED100); } -static int ksz886x_cable_test_result_trans(u16 status) +static int ksz886x_cable_test_result_trans(u16 status, u16 mask) { - switch (FIELD_GET(KSZ8081_LMD_STAT_MASK, status)) { + switch (FIELD_GET(mask, status)) { case KSZ8081_LMD_STAT_NORMAL: return ETHTOOL_A_CABLE_RESULT_CODE_OK; case KSZ8081_LMD_STAT_SHORT: @@ -1825,15 +1854,15 @@ static int ksz886x_cable_test_result_trans(u16 status) } } -static bool ksz886x_cable_test_failed(u16 status) +static bool ksz886x_cable_test_failed(u16 status, u16 mask) { - return FIELD_GET(KSZ8081_LMD_STAT_MASK, status) == + return FIELD_GET(mask, status) == KSZ8081_LMD_STAT_FAIL; } -static bool ksz886x_cable_test_fault_length_valid(u16 status) +static bool ksz886x_cable_test_fault_length_valid(u16 status, u16 mask) { - switch (FIELD_GET(KSZ8081_LMD_STAT_MASK, status)) { + switch (FIELD_GET(mask, status)) { case KSZ8081_LMD_STAT_OPEN: fallthrough; case KSZ8081_LMD_STAT_SHORT: @@ -1842,29 +1871,79 @@ static bool ksz886x_cable_test_fault_length_valid(u16 status) return false; } -static int ksz886x_cable_test_fault_length(u16 status) +static int ksz886x_cable_test_fault_length(struct phy_device *phydev, u16 status, u16 data_mask) { int dt; /* According to the data sheet the distance to the fault is - * DELTA_TIME * 0.4 meters. + * DELTA_TIME * 0.4 meters for ksz phys. + * (DELTA_TIME - 22) * 0.8 for lan8814 phy. */ - dt = FIELD_GET(KSZ8081_LMD_DELTA_TIME_MASK, status); + dt = FIELD_GET(data_mask, status); - return (dt * 400) / 10; + if ((phydev->phy_id & MICREL_PHY_ID_MASK) == PHY_ID_LAN8814) + return ((dt - 22) * 800) / 10; + else + return (dt * 400) / 10; } static int ksz886x_cable_test_wait_for_completion(struct phy_device *phydev) { + const struct kszphy_type *type = phydev->drv->driver_data; int val, ret; - ret = phy_read_poll_timeout(phydev, KSZ8081_LMD, val, + ret = phy_read_poll_timeout(phydev, type->cable_diag_reg, val, !(val & KSZ8081_LMD_ENABLE_TEST), 30000, 100000, true); return ret < 0 ? ret : 0; } +static int lan8814_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, + ETHTOOL_A_CABLE_PAIR_C, + ETHTOOL_A_CABLE_PAIR_D, + }; + u32 fault_length; + int ret; + int val; + + val = KSZ8081_LMD_ENABLE_TEST; + val = val | (pair << LAN8814_PAIR_BIT_SHIFT); + + ret = phy_write(phydev, LAN8814_CABLE_DIAG, val); + if (ret < 0) + return ret; + + ret = ksz886x_cable_test_wait_for_completion(phydev); + if (ret) + return ret; + + val = phy_read(phydev, LAN8814_CABLE_DIAG); + if (val < 0) + return val; + + if (ksz886x_cable_test_failed(val, LAN8814_CABLE_DIAG_STAT_MASK)) + return -EAGAIN; + + ret = ethnl_cable_test_result(phydev, ethtool_pair[pair], + ksz886x_cable_test_result_trans(val, + LAN8814_CABLE_DIAG_STAT_MASK + )); + if (ret) + return ret; + + if (!ksz886x_cable_test_fault_length_valid(val, LAN8814_CABLE_DIAG_STAT_MASK)) + return 0; + + fault_length = ksz886x_cable_test_fault_length(phydev, val, + LAN8814_CABLE_DIAG_VCT_DATA_MASK); + + return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair], fault_length); +} + static int ksz886x_cable_test_one_pair(struct phy_device *phydev, int pair) { static const int ethtool_pair[] = { @@ -1872,6 +1951,7 @@ static int ksz886x_cable_test_one_pair(struct phy_device *phydev, int pair) ETHTOOL_A_CABLE_PAIR_B, }; int ret, val, mdix; + u32 fault_length; /* There is no way to choice the pair, like we do one ksz9031. * We can workaround this limitation by using the MDI-X functionality. @@ -1910,25 +1990,27 @@ static int ksz886x_cable_test_one_pair(struct phy_device *phydev, int pair) if (val < 0) return val; - if (ksz886x_cable_test_failed(val)) + if (ksz886x_cable_test_failed(val, KSZ8081_LMD_STAT_MASK)) return -EAGAIN; ret = ethnl_cable_test_result(phydev, ethtool_pair[pair], - ksz886x_cable_test_result_trans(val)); + ksz886x_cable_test_result_trans(val, KSZ8081_LMD_STAT_MASK)); if (ret) return ret; - if (!ksz886x_cable_test_fault_length_valid(val)) + if (!ksz886x_cable_test_fault_length_valid(val, KSZ8081_LMD_STAT_MASK)) return 0; - return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair], - ksz886x_cable_test_fault_length(val)); + fault_length = ksz886x_cable_test_fault_length(phydev, val, KSZ8081_LMD_DELTA_TIME_MASK); + + return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair], fault_length); } static int ksz886x_cable_test_get_status(struct phy_device *phydev, bool *finished) { - unsigned long pair_mask = 0x3; + const struct kszphy_type *type = phydev->drv->driver_data; + unsigned long pair_mask = type->pair_mask; int retries = 20; int pair, ret; @@ -1937,7 +2019,10 @@ static int ksz886x_cable_test_get_status(struct phy_device *phydev, /* 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 (type->cable_diag_reg == LAN8814_CABLE_DIAG) + ret = lan8814_cable_test_one_pair(phydev, pair); + else + ret = ksz886x_cable_test_one_pair(phydev, pair); if (ret == -EAGAIN) continue; if (ret < 0) @@ -3111,6 +3196,7 @@ static struct phy_driver ksphy_driver[] = { .phy_id = PHY_ID_LAN8814, .phy_id_mask = MICREL_PHY_ID_MASK, .name = "Microchip INDY Gigabit Quad PHY", + .flags = PHY_POLL_CABLE_TEST, .config_init = lan8814_config_init, .driver_data = &lan8814_type, .probe = lan8814_probe, @@ -3123,6 +3209,8 @@ static struct phy_driver ksphy_driver[] = { .resume = kszphy_resume, .config_intr = lan8814_config_intr, .handle_interrupt = lan8814_handle_interrupt, + .cable_test_start = lan8814_cable_test_start, + .cable_test_get_status = ksz886x_cable_test_get_status, }, { .phy_id = PHY_ID_LAN8804, .phy_id_mask = MICREL_PHY_ID_MASK, @@ -3169,6 +3257,7 @@ static struct phy_driver ksphy_driver[] = { .phy_id = PHY_ID_KSZ886X, .phy_id_mask = MICREL_PHY_ID_MASK, .name = "Micrel KSZ8851 Ethernet MAC or KSZ886X Switch", + .driver_data = &ksz886x_type, /* PHY_BASIC_FEATURES */ .flags = PHY_POLL_CABLE_TEST, .config_init = kszphy_config_init, |