From 58581478a734b07eb27032299fcb36caa4f9988b Mon Sep 17 00:00:00 2001 From: Ivan Bornyakov Date: Tue, 13 Apr 2021 23:54:50 +0300 Subject: [PATCH 1/3] net: phy: marvell-88x2222: check that link is operational Some SFP modules uses RX_LOS for link indication. In such cases link will be always up, even without cable connected. RX_LOS changes will trigger link_up()/link_down() upstream operations. Thus, check that SFP link is operational before actual read link status. If there is no SFP cage connected to the tranciever, check only PMD Recieve Signal Detect register. Signed-off-by: Ivan Bornyakov Signed-off-by: David S. Miller --- drivers/net/phy/marvell-88x2222.c | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index eca8c2f20684..28fe520228a4 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -32,6 +32,10 @@ #define MV_HOST_RST_SW BIT(7) #define MV_PORT_RST_SW (MV_LINE_RST_SW | MV_HOST_RST_SW) +/* PMD Receive Signal Detect */ +#define MV_RX_SIGNAL_DETECT 0x000A +#define MV_RX_SIGNAL_DETECT_GLOBAL BIT(0) + /* 1000Base-X/SGMII Control Register */ #define MV_1GBX_CTRL (0x2000 + MII_BMCR) @@ -51,6 +55,7 @@ struct mv2222_data { phy_interface_t line_interface; __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); + bool sfp_link; }; /* SFI PMA transmit enable */ @@ -139,6 +144,21 @@ static int mv2222_read_status_1g(struct phy_device *phydev) return link; } +static bool mv2222_link_is_operational(struct phy_device *phydev) +{ + struct mv2222_data *priv = phydev->priv; + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_RX_SIGNAL_DETECT); + if (val < 0 || !(val & MV_RX_SIGNAL_DETECT_GLOBAL)) + return false; + + if (phydev->sfp_bus && !priv->sfp_link) + return false; + + return true; +} + static int mv2222_read_status(struct phy_device *phydev) { struct mv2222_data *priv = phydev->priv; @@ -148,6 +168,9 @@ static int mv2222_read_status(struct phy_device *phydev) phydev->speed = SPEED_UNKNOWN; phydev->duplex = DUPLEX_UNKNOWN; + if (!mv2222_link_is_operational(phydev)) + return 0; + if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) link = mv2222_read_status_10g(phydev); else @@ -446,9 +469,29 @@ static void mv2222_sfp_remove(void *upstream) linkmode_zero(priv->supported); } +static void mv2222_sfp_link_up(void *upstream) +{ + struct phy_device *phydev = upstream; + struct mv2222_data *priv; + + priv = phydev->priv; + priv->sfp_link = true; +} + +static void mv2222_sfp_link_down(void *upstream) +{ + struct phy_device *phydev = upstream; + struct mv2222_data *priv; + + priv = phydev->priv; + priv->sfp_link = false; +} + static const struct sfp_upstream_ops sfp_phy_ops = { .module_insert = mv2222_sfp_insert, .module_remove = mv2222_sfp_remove, + .link_up = mv2222_sfp_link_up, + .link_down = mv2222_sfp_link_down, .attach = phy_sfp_attach, .detach = phy_sfp_detach, }; From 473960a7b4434dbda6f628eb9e29e989f730343f Mon Sep 17 00:00:00 2001 From: Ivan Bornyakov Date: Tue, 13 Apr 2021 23:54:51 +0300 Subject: [PATCH 2/3] net: phy: marvell-88x2222: move read_status after config_aneg No functional changes, just move read link status routines below autonegotiation configuration to make future functional changes more distinct. Signed-off-by: Ivan Bornyakov Signed-off-by: David S. Miller --- drivers/net/phy/marvell-88x2222.c | 196 +++++++++++++++--------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 28fe520228a4..640b133f1371 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -86,104 +86,6 @@ static int mv2222_soft_reset(struct phy_device *phydev) 5000, 1000000, true); } -/* Returns negative on error, 0 if link is down, 1 if link is up */ -static int mv2222_read_status_10g(struct phy_device *phydev) -{ - int val, link = 0; - - val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1); - if (val < 0) - return val; - - if (val & MDIO_STAT1_LSTATUS) { - link = 1; - - /* 10GBASE-R do not support auto-negotiation */ - phydev->autoneg = AUTONEG_DISABLE; - phydev->speed = SPEED_10000; - phydev->duplex = DUPLEX_FULL; - } - - return link; -} - -/* Returns negative on error, 0 if link is down, 1 if link is up */ -static int mv2222_read_status_1g(struct phy_device *phydev) -{ - int val, link = 0; - - val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT); - if (val < 0) - return val; - - if (!(val & BMSR_LSTATUS) || - (phydev->autoneg == AUTONEG_ENABLE && - !(val & BMSR_ANEGCOMPLETE))) - return 0; - - link = 1; - - val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_PHY_STAT); - if (val < 0) - return val; - - if (val & MV_1GBX_PHY_STAT_AN_RESOLVED) { - if (val & MV_1GBX_PHY_STAT_DUPLEX) - phydev->duplex = DUPLEX_FULL; - else - phydev->duplex = DUPLEX_HALF; - - if (val & MV_1GBX_PHY_STAT_SPEED1000) - phydev->speed = SPEED_1000; - else if (val & MV_1GBX_PHY_STAT_SPEED100) - phydev->speed = SPEED_100; - else - phydev->speed = SPEED_10; - } - - return link; -} - -static bool mv2222_link_is_operational(struct phy_device *phydev) -{ - struct mv2222_data *priv = phydev->priv; - int val; - - val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_RX_SIGNAL_DETECT); - if (val < 0 || !(val & MV_RX_SIGNAL_DETECT_GLOBAL)) - return false; - - if (phydev->sfp_bus && !priv->sfp_link) - return false; - - return true; -} - -static int mv2222_read_status(struct phy_device *phydev) -{ - struct mv2222_data *priv = phydev->priv; - int link; - - phydev->link = 0; - phydev->speed = SPEED_UNKNOWN; - phydev->duplex = DUPLEX_UNKNOWN; - - if (!mv2222_link_is_operational(phydev)) - return 0; - - if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) - link = mv2222_read_status_10g(phydev); - else - link = mv2222_read_status_1g(phydev); - - if (link < 0) - return link; - - phydev->link = link; - - return 0; -} - static int mv2222_disable_aneg(struct phy_device *phydev) { int ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_CTRL, @@ -386,6 +288,104 @@ static int mv2222_aneg_done(struct phy_device *phydev) return (ret & BMSR_ANEGCOMPLETE); } +/* Returns negative on error, 0 if link is down, 1 if link is up */ +static int mv2222_read_status_10g(struct phy_device *phydev) +{ + int val, link = 0; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1); + if (val < 0) + return val; + + if (val & MDIO_STAT1_LSTATUS) { + link = 1; + + /* 10GBASE-R do not support auto-negotiation */ + phydev->autoneg = AUTONEG_DISABLE; + phydev->speed = SPEED_10000; + phydev->duplex = DUPLEX_FULL; + } + + return link; +} + +/* Returns negative on error, 0 if link is down, 1 if link is up */ +static int mv2222_read_status_1g(struct phy_device *phydev) +{ + int val, link = 0; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT); + if (val < 0) + return val; + + if (!(val & BMSR_LSTATUS) || + (phydev->autoneg == AUTONEG_ENABLE && + !(val & BMSR_ANEGCOMPLETE))) + return 0; + + link = 1; + + val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_PHY_STAT); + if (val < 0) + return val; + + if (val & MV_1GBX_PHY_STAT_AN_RESOLVED) { + if (val & MV_1GBX_PHY_STAT_DUPLEX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + + if (val & MV_1GBX_PHY_STAT_SPEED1000) + phydev->speed = SPEED_1000; + else if (val & MV_1GBX_PHY_STAT_SPEED100) + phydev->speed = SPEED_100; + else + phydev->speed = SPEED_10; + } + + return link; +} + +static bool mv2222_link_is_operational(struct phy_device *phydev) +{ + struct mv2222_data *priv = phydev->priv; + int val; + + val = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MV_RX_SIGNAL_DETECT); + if (val < 0 || !(val & MV_RX_SIGNAL_DETECT_GLOBAL)) + return false; + + if (phydev->sfp_bus && !priv->sfp_link) + return false; + + return true; +} + +static int mv2222_read_status(struct phy_device *phydev) +{ + struct mv2222_data *priv = phydev->priv; + int link; + + phydev->link = 0; + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + + if (!mv2222_link_is_operational(phydev)) + return 0; + + if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) + link = mv2222_read_status_10g(phydev); + else + link = mv2222_read_status_1g(phydev); + + if (link < 0) + return link; + + phydev->link = link; + + return 0; +} + static int mv2222_resume(struct phy_device *phydev) { return mv2222_tx_enable(phydev); From d7029f55cc46066d833cbf7f532b1ae8d6835859 Mon Sep 17 00:00:00 2001 From: Ivan Bornyakov Date: Tue, 13 Apr 2021 23:54:52 +0300 Subject: [PATCH 3/3] net: phy: marvell-88x2222: swap 1G/10G modes on autoneg Setting 10G without autonegotiation is invalid according to phy_ethtool_ksettings_set(). Thus, we need to set it during autonegotiation. If 1G autonegotiation can't complete for quite a time, but there is signal in line, switch line interface type to 10GBase-R, if supported, in hope for link to be established. And vice versa. If 10GBase-R link can't be established for quite a time, and autonegotiation is enabled, and there is signal in line, switch line interface type to appropriate 1G mode, i.e. 1000Base-X or SGMII, if supported. Signed-off-by: Ivan Bornyakov Signed-off-by: David S. Miller --- drivers/net/phy/marvell-88x2222.c | 117 +++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 28 deletions(-) diff --git a/drivers/net/phy/marvell-88x2222.c b/drivers/net/phy/marvell-88x2222.c index 640b133f1371..9b9ac3ef735d 100644 --- a/drivers/net/phy/marvell-88x2222.c +++ b/drivers/net/phy/marvell-88x2222.c @@ -52,6 +52,8 @@ #define MV_1GBX_PHY_STAT_SPEED100 BIT(14) #define MV_1GBX_PHY_STAT_SPEED1000 BIT(15) +#define AUTONEG_TIMEOUT 3 + struct mv2222_data { phy_interface_t line_interface; __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); @@ -173,6 +175,24 @@ static bool mv2222_is_1gbx_capable(struct phy_device *phydev) priv->supported); } +static bool mv2222_is_sgmii_capable(struct phy_device *phydev) +{ + struct mv2222_data *priv = phydev->priv; + + return (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, + priv->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, + priv->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, + priv->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, + priv->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, + priv->supported) || + linkmode_test_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, + priv->supported)); +} + static int mv2222_config_line(struct phy_device *phydev) { struct mv2222_data *priv = phydev->priv; @@ -192,7 +212,8 @@ static int mv2222_config_line(struct phy_device *phydev) } } -static int mv2222_setup_forced(struct phy_device *phydev) +/* Switch between 1G (1000Base-X/SGMII) and 10G (10GBase-R) modes */ +static int mv2222_swap_line_type(struct phy_device *phydev) { struct mv2222_data *priv = phydev->priv; bool changed = false; @@ -200,26 +221,24 @@ static int mv2222_setup_forced(struct phy_device *phydev) switch (priv->line_interface) { case PHY_INTERFACE_MODE_10GBASER: - if (phydev->speed == SPEED_1000 && - mv2222_is_1gbx_capable(phydev)) { + if (mv2222_is_1gbx_capable(phydev)) { priv->line_interface = PHY_INTERFACE_MODE_1000BASEX; changed = true; } + if (mv2222_is_sgmii_capable(phydev)) { + priv->line_interface = PHY_INTERFACE_MODE_SGMII; + changed = true; + } + break; case PHY_INTERFACE_MODE_1000BASEX: - if (phydev->speed == SPEED_10000 && - mv2222_is_10g_capable(phydev)) { + case PHY_INTERFACE_MODE_SGMII: + if (mv2222_is_10g_capable(phydev)) { priv->line_interface = PHY_INTERFACE_MODE_10GBASER; changed = true; } - break; - case PHY_INTERFACE_MODE_SGMII: - ret = mv2222_set_sgmii_speed(phydev); - if (ret < 0) - return ret; - break; default: return -EINVAL; @@ -231,6 +250,29 @@ static int mv2222_setup_forced(struct phy_device *phydev) return ret; } + return 0; +} + +static int mv2222_setup_forced(struct phy_device *phydev) +{ + struct mv2222_data *priv = phydev->priv; + int ret; + + if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) { + if (phydev->speed < SPEED_10000 && + phydev->speed != SPEED_UNKNOWN) { + ret = mv2222_swap_line_type(phydev); + if (ret < 0) + return ret; + } + } + + if (priv->line_interface == PHY_INTERFACE_MODE_SGMII) { + ret = mv2222_set_sgmii_speed(phydev); + if (ret < 0) + return ret; + } + return mv2222_disable_aneg(phydev); } @@ -244,17 +286,9 @@ static int mv2222_config_aneg(struct phy_device *phydev) return 0; if (phydev->autoneg == AUTONEG_DISABLE || - phydev->speed == SPEED_10000) + priv->line_interface == PHY_INTERFACE_MODE_10GBASER) return mv2222_setup_forced(phydev); - if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER && - mv2222_is_1gbx_capable(phydev)) { - priv->line_interface = PHY_INTERFACE_MODE_1000BASEX; - ret = mv2222_config_line(phydev); - if (ret < 0) - return ret; - } - adv = linkmode_adv_to_mii_adv_x(priv->supported, ETHTOOL_LINK_MODE_1000baseX_Full_BIT); @@ -291,6 +325,7 @@ static int mv2222_aneg_done(struct phy_device *phydev) /* Returns negative on error, 0 if link is down, 1 if link is up */ static int mv2222_read_status_10g(struct phy_device *phydev) { + static int timeout; int val, link = 0; val = phy_read_mmd(phydev, MDIO_MMD_PCS, MDIO_STAT1); @@ -304,6 +339,20 @@ static int mv2222_read_status_10g(struct phy_device *phydev) phydev->autoneg = AUTONEG_DISABLE; phydev->speed = SPEED_10000; phydev->duplex = DUPLEX_FULL; + } else { + if (phydev->autoneg == AUTONEG_ENABLE) { + timeout++; + + if (timeout > AUTONEG_TIMEOUT) { + timeout = 0; + + val = mv2222_swap_line_type(phydev); + if (val < 0) + return val; + + return mv2222_config_aneg(phydev); + } + } } return link; @@ -312,15 +361,31 @@ static int mv2222_read_status_10g(struct phy_device *phydev) /* Returns negative on error, 0 if link is down, 1 if link is up */ static int mv2222_read_status_1g(struct phy_device *phydev) { + static int timeout; int val, link = 0; val = phy_read_mmd(phydev, MDIO_MMD_PCS, MV_1GBX_STAT); if (val < 0) return val; - if (!(val & BMSR_LSTATUS) || - (phydev->autoneg == AUTONEG_ENABLE && - !(val & BMSR_ANEGCOMPLETE))) + if (phydev->autoneg == AUTONEG_ENABLE && + !(val & BMSR_ANEGCOMPLETE)) { + timeout++; + + if (timeout > AUTONEG_TIMEOUT) { + timeout = 0; + + val = mv2222_swap_line_type(phydev); + if (val < 0) + return val; + + return mv2222_config_aneg(phydev); + } + + return 0; + } + + if (!(val & BMSR_LSTATUS)) return 0; link = 1; @@ -447,11 +512,7 @@ static int mv2222_sfp_insert(void *upstream, const struct sfp_eeprom_id *id) return ret; if (mutex_trylock(&phydev->lock)) { - if (priv->line_interface == PHY_INTERFACE_MODE_10GBASER) - ret = mv2222_setup_forced(phydev); - else - ret = mv2222_config_aneg(phydev); - + ret = mv2222_config_aneg(phydev); mutex_unlock(&phydev->lock); }