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>
This commit is contained in:
parent
517ff3ceb3
commit
21b688dabe
1 changed files with 107 additions and 18 deletions
|
@ -92,6 +92,15 @@
|
||||||
#define KSZ9x31_LMD_VCT_DATA_HI_PULSE_MASK GENMASK(1, 0)
|
#define KSZ9x31_LMD_VCT_DATA_HI_PULSE_MASK GENMASK(1, 0)
|
||||||
#define KSZ9x31_LMD_VCT_DATA_MASK GENMASK(7, 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. */
|
/* Lan8814 general Interrupt control/status reg in GPHY specific block. */
|
||||||
#define LAN8814_INTC 0x18
|
#define LAN8814_INTC 0x18
|
||||||
#define LAN8814_INTS 0x1B
|
#define LAN8814_INTS 0x1B
|
||||||
|
@ -257,6 +266,8 @@ static struct kszphy_hw_stat kszphy_hw_stats[] = {
|
||||||
struct kszphy_type {
|
struct kszphy_type {
|
||||||
u32 led_mode_reg;
|
u32 led_mode_reg;
|
||||||
u16 interrupt_level_mask;
|
u16 interrupt_level_mask;
|
||||||
|
u16 cable_diag_reg;
|
||||||
|
unsigned long pair_mask;
|
||||||
bool has_broadcast_disable;
|
bool has_broadcast_disable;
|
||||||
bool has_nand_tree_disable;
|
bool has_nand_tree_disable;
|
||||||
bool has_rmii_ref_clk_sel;
|
bool has_rmii_ref_clk_sel;
|
||||||
|
@ -313,6 +324,13 @@ struct kszphy_priv {
|
||||||
|
|
||||||
static const struct kszphy_type lan8814_type = {
|
static const struct kszphy_type lan8814_type = {
|
||||||
.led_mode_reg = ~LAN8814_LED_CTRL_1,
|
.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 = {
|
static const struct kszphy_type ksz8021_type = {
|
||||||
|
@ -1796,6 +1814,17 @@ static int kszphy_probe(struct phy_device *phydev)
|
||||||
return 0;
|
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)
|
static int ksz886x_cable_test_start(struct phy_device *phydev)
|
||||||
{
|
{
|
||||||
if (phydev->dev_flags & MICREL_KSZ8_P1_ERRATA)
|
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);
|
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:
|
case KSZ8081_LMD_STAT_NORMAL:
|
||||||
return ETHTOOL_A_CABLE_RESULT_CODE_OK;
|
return ETHTOOL_A_CABLE_RESULT_CODE_OK;
|
||||||
case KSZ8081_LMD_STAT_SHORT:
|
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;
|
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:
|
case KSZ8081_LMD_STAT_OPEN:
|
||||||
fallthrough;
|
fallthrough;
|
||||||
case KSZ8081_LMD_STAT_SHORT:
|
case KSZ8081_LMD_STAT_SHORT:
|
||||||
|
@ -1842,29 +1871,79 @@ static bool ksz886x_cable_test_fault_length_valid(u16 status)
|
||||||
return false;
|
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;
|
int dt;
|
||||||
|
|
||||||
/* According to the data sheet the distance to the fault is
|
/* 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);
|
||||||
|
|
||||||
|
if ((phydev->phy_id & MICREL_PHY_ID_MASK) == PHY_ID_LAN8814)
|
||||||
|
return ((dt - 22) * 800) / 10;
|
||||||
|
else
|
||||||
return (dt * 400) / 10;
|
return (dt * 400) / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ksz886x_cable_test_wait_for_completion(struct phy_device *phydev)
|
static int ksz886x_cable_test_wait_for_completion(struct phy_device *phydev)
|
||||||
{
|
{
|
||||||
|
const struct kszphy_type *type = phydev->drv->driver_data;
|
||||||
int val, ret;
|
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),
|
!(val & KSZ8081_LMD_ENABLE_TEST),
|
||||||
30000, 100000, true);
|
30000, 100000, true);
|
||||||
|
|
||||||
return ret < 0 ? ret : 0;
|
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 int ksz886x_cable_test_one_pair(struct phy_device *phydev, int pair)
|
||||||
{
|
{
|
||||||
static const int ethtool_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,
|
ETHTOOL_A_CABLE_PAIR_B,
|
||||||
};
|
};
|
||||||
int ret, val, mdix;
|
int ret, val, mdix;
|
||||||
|
u32 fault_length;
|
||||||
|
|
||||||
/* There is no way to choice the pair, like we do one ksz9031.
|
/* There is no way to choice the pair, like we do one ksz9031.
|
||||||
* We can workaround this limitation by using the MDI-X functionality.
|
* 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)
|
if (val < 0)
|
||||||
return val;
|
return val;
|
||||||
|
|
||||||
if (ksz886x_cable_test_failed(val))
|
if (ksz886x_cable_test_failed(val, KSZ8081_LMD_STAT_MASK))
|
||||||
return -EAGAIN;
|
return -EAGAIN;
|
||||||
|
|
||||||
ret = ethnl_cable_test_result(phydev, ethtool_pair[pair],
|
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)
|
if (ret)
|
||||||
return 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 0;
|
||||||
|
|
||||||
return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair],
|
fault_length = ksz886x_cable_test_fault_length(phydev, val, KSZ8081_LMD_DELTA_TIME_MASK);
|
||||||
ksz886x_cable_test_fault_length(val));
|
|
||||||
|
return ethnl_cable_test_fault_length(phydev, ethtool_pair[pair], fault_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ksz886x_cable_test_get_status(struct phy_device *phydev,
|
static int ksz886x_cable_test_get_status(struct phy_device *phydev,
|
||||||
bool *finished)
|
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 retries = 20;
|
||||||
int pair, ret;
|
int pair, ret;
|
||||||
|
|
||||||
|
@ -1937,6 +2019,9 @@ static int ksz886x_cable_test_get_status(struct phy_device *phydev,
|
||||||
/* Try harder if link partner is active */
|
/* Try harder if link partner is active */
|
||||||
while (pair_mask && retries--) {
|
while (pair_mask && retries--) {
|
||||||
for_each_set_bit(pair, &pair_mask, 4) {
|
for_each_set_bit(pair, &pair_mask, 4) {
|
||||||
|
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);
|
ret = ksz886x_cable_test_one_pair(phydev, pair);
|
||||||
if (ret == -EAGAIN)
|
if (ret == -EAGAIN)
|
||||||
continue;
|
continue;
|
||||||
|
@ -3111,6 +3196,7 @@ static struct phy_driver ksphy_driver[] = {
|
||||||
.phy_id = PHY_ID_LAN8814,
|
.phy_id = PHY_ID_LAN8814,
|
||||||
.phy_id_mask = MICREL_PHY_ID_MASK,
|
.phy_id_mask = MICREL_PHY_ID_MASK,
|
||||||
.name = "Microchip INDY Gigabit Quad PHY",
|
.name = "Microchip INDY Gigabit Quad PHY",
|
||||||
|
.flags = PHY_POLL_CABLE_TEST,
|
||||||
.config_init = lan8814_config_init,
|
.config_init = lan8814_config_init,
|
||||||
.driver_data = &lan8814_type,
|
.driver_data = &lan8814_type,
|
||||||
.probe = lan8814_probe,
|
.probe = lan8814_probe,
|
||||||
|
@ -3123,6 +3209,8 @@ static struct phy_driver ksphy_driver[] = {
|
||||||
.resume = kszphy_resume,
|
.resume = kszphy_resume,
|
||||||
.config_intr = lan8814_config_intr,
|
.config_intr = lan8814_config_intr,
|
||||||
.handle_interrupt = lan8814_handle_interrupt,
|
.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 = PHY_ID_LAN8804,
|
||||||
.phy_id_mask = MICREL_PHY_ID_MASK,
|
.phy_id_mask = MICREL_PHY_ID_MASK,
|
||||||
|
@ -3169,6 +3257,7 @@ static struct phy_driver ksphy_driver[] = {
|
||||||
.phy_id = PHY_ID_KSZ886X,
|
.phy_id = PHY_ID_KSZ886X,
|
||||||
.phy_id_mask = MICREL_PHY_ID_MASK,
|
.phy_id_mask = MICREL_PHY_ID_MASK,
|
||||||
.name = "Micrel KSZ8851 Ethernet MAC or KSZ886X Switch",
|
.name = "Micrel KSZ8851 Ethernet MAC or KSZ886X Switch",
|
||||||
|
.driver_data = &ksz886x_type,
|
||||||
/* PHY_BASIC_FEATURES */
|
/* PHY_BASIC_FEATURES */
|
||||||
.flags = PHY_POLL_CABLE_TEST,
|
.flags = PHY_POLL_CABLE_TEST,
|
||||||
.config_init = kszphy_config_init,
|
.config_init = kszphy_config_init,
|
||||||
|
|
Loading…
Add table
Reference in a new issue