| From fa6e98cee558622565c97924e922b97340aeabd8 Mon Sep 17 00:00:00 2001 |
| From: Heiner Kallweit <hkallweit1@gmail.com> |
| Date: Tue, 22 Oct 2019 11:31:07 -0700 |
| Subject: [PATCH] net: phy: add support for clause 37 auto-negotiation |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| This patch adds support for clause 37 1000Base-X auto-negotiation. |
| |
| Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com> |
| Signed-off-by: Tao Ren <taoren@fb.com> |
| Tested-by: René van Dorst <opensource@vdorst.com> |
| Reviewed-by: Andrew Lunn <andrew@lunn.ch> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| --- |
| drivers/net/phy/phy_device.c | 139 +++++++++++++++++++++++++++++++++++ |
| include/linux/phy.h | 4 + |
| 2 files changed, 143 insertions(+) |
| |
| --- a/drivers/net/phy/phy_device.c |
| +++ b/drivers/net/phy/phy_device.c |
| @@ -1683,6 +1683,40 @@ static int genphy_config_advert(struct p |
| } |
| |
| /** |
| + * genphy_c37_config_advert - sanitize and advertise auto-negotiation parameters |
| + * @phydev: target phy_device struct |
| + * |
| + * Description: Writes MII_ADVERTISE with the appropriate values, |
| + * after sanitizing the values to make sure we only advertise |
| + * what is supported. Returns < 0 on error, 0 if the PHY's advertisement |
| + * hasn't changed, and > 0 if it has changed. This function is intended |
| + * for Clause 37 1000Base-X mode. |
| + */ |
| +static int genphy_c37_config_advert(struct phy_device *phydev) |
| +{ |
| + u16 adv = 0; |
| + |
| + /* Only allow advertising what this PHY supports */ |
| + linkmode_and(phydev->advertising, phydev->advertising, |
| + phydev->supported); |
| + |
| + if (linkmode_test_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, |
| + phydev->advertising)) |
| + adv |= ADVERTISE_1000XFULL; |
| + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, |
| + phydev->advertising)) |
| + adv |= ADVERTISE_1000XPAUSE; |
| + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, |
| + phydev->advertising)) |
| + adv |= ADVERTISE_1000XPSE_ASYM; |
| + |
| + return phy_modify_changed(phydev, MII_ADVERTISE, |
| + ADVERTISE_1000XFULL | ADVERTISE_1000XPAUSE | |
| + ADVERTISE_1000XHALF | ADVERTISE_1000XPSE_ASYM, |
| + adv); |
| +} |
| + |
| +/** |
| * genphy_config_eee_advert - disable unwanted eee mode advertisement |
| * @phydev: target phy_device struct |
| * |
| @@ -1791,6 +1825,54 @@ int __genphy_config_aneg(struct phy_devi |
| EXPORT_SYMBOL(__genphy_config_aneg); |
| |
| /** |
| + * genphy_c37_config_aneg - restart auto-negotiation or write BMCR |
| + * @phydev: target phy_device struct |
| + * |
| + * Description: If auto-negotiation is enabled, we configure the |
| + * advertising, and then restart auto-negotiation. If it is not |
| + * enabled, then we write the BMCR. This function is intended |
| + * for use with Clause 37 1000Base-X mode. |
| + */ |
| +int genphy_c37_config_aneg(struct phy_device *phydev) |
| +{ |
| + int err, changed; |
| + |
| + if (phydev->autoneg != AUTONEG_ENABLE) |
| + return genphy_setup_forced(phydev); |
| + |
| + err = phy_modify(phydev, MII_BMCR, BMCR_SPEED1000 | BMCR_SPEED100, |
| + BMCR_SPEED1000); |
| + if (err) |
| + return err; |
| + |
| + changed = genphy_c37_config_advert(phydev); |
| + if (changed < 0) /* error */ |
| + return changed; |
| + |
| + if (!changed) { |
| + /* Advertisement hasn't changed, but maybe aneg was never on to |
| + * begin with? Or maybe phy was isolated? |
| + */ |
| + int ctl = phy_read(phydev, MII_BMCR); |
| + |
| + if (ctl < 0) |
| + return ctl; |
| + |
| + if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE)) |
| + changed = 1; /* do restart aneg */ |
| + } |
| + |
| + /* Only restart aneg if we are advertising something different |
| + * than we were before. |
| + */ |
| + if (changed > 0) |
| + return genphy_restart_aneg(phydev); |
| + |
| + return 0; |
| +} |
| +EXPORT_SYMBOL(genphy_c37_config_aneg); |
| + |
| +/** |
| * genphy_aneg_done - return auto-negotiation status |
| * @phydev: target phy_device struct |
| * |
| @@ -1963,6 +2045,63 @@ int genphy_read_status(struct phy_device |
| EXPORT_SYMBOL(genphy_read_status); |
| |
| /** |
| + * genphy_c37_read_status - check the link status and update current link state |
| + * @phydev: target phy_device struct |
| + * |
| + * Description: Check the link, then figure out the current state |
| + * by comparing what we advertise with what the link partner |
| + * advertises. This function is for Clause 37 1000Base-X mode. |
| + */ |
| +int genphy_c37_read_status(struct phy_device *phydev) |
| +{ |
| + int lpa, err, old_link = phydev->link; |
| + |
| + /* Update the link, but return if there was an error */ |
| + err = genphy_update_link(phydev); |
| + if (err) |
| + return err; |
| + |
| + /* why bother the PHY if nothing can have changed */ |
| + if (phydev->autoneg == AUTONEG_ENABLE && old_link && phydev->link) |
| + return 0; |
| + |
| + phydev->duplex = DUPLEX_UNKNOWN; |
| + phydev->pause = 0; |
| + phydev->asym_pause = 0; |
| + |
| + if (phydev->autoneg == AUTONEG_ENABLE && phydev->autoneg_complete) { |
| + lpa = phy_read(phydev, MII_LPA); |
| + if (lpa < 0) |
| + return lpa; |
| + |
| + linkmode_mod_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, |
| + phydev->lp_advertising, lpa & LPA_LPACK); |
| + linkmode_mod_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, |
| + phydev->lp_advertising, lpa & LPA_1000XFULL); |
| + linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, |
| + phydev->lp_advertising, lpa & LPA_1000XPAUSE); |
| + linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, |
| + phydev->lp_advertising, |
| + lpa & LPA_1000XPAUSE_ASYM); |
| + |
| + phy_resolve_aneg_linkmode(phydev); |
| + } else if (phydev->autoneg == AUTONEG_DISABLE) { |
| + int bmcr = phy_read(phydev, MII_BMCR); |
| + |
| + if (bmcr < 0) |
| + return bmcr; |
| + |
| + if (bmcr & BMCR_FULLDPLX) |
| + phydev->duplex = DUPLEX_FULL; |
| + else |
| + phydev->duplex = DUPLEX_HALF; |
| + } |
| + |
| + return 0; |
| +} |
| +EXPORT_SYMBOL(genphy_c37_read_status); |
| + |
| +/** |
| * genphy_soft_reset - software reset the PHY via BMCR_RESET bit |
| * @phydev: target phy_device struct |
| * |
| --- a/include/linux/phy.h |
| +++ b/include/linux/phy.h |
| @@ -1120,6 +1120,10 @@ int genphy_read_mmd_unsupported(struct p |
| int genphy_write_mmd_unsupported(struct phy_device *phdev, int devnum, |
| u16 regnum, u16 val); |
| |
| +/* Clause 37 */ |
| +int genphy_c37_config_aneg(struct phy_device *phydev); |
| +int genphy_c37_read_status(struct phy_device *phydev); |
| + |
| /* Clause 45 PHY */ |
| int genphy_c45_restart_aneg(struct phy_device *phydev); |
| int genphy_c45_check_and_restart_aneg(struct phy_device *phydev, bool restart); |