diff options
Diffstat (limited to 'drivers/net/phy')
-rw-r--r-- | drivers/net/phy/Kconfig | 4 | ||||
-rw-r--r-- | drivers/net/phy/Makefile | 3 | ||||
-rw-r--r-- | drivers/net/phy/broadcom.c | 24 | ||||
-rw-r--r-- | drivers/net/phy/dp83867.c | 150 | ||||
-rw-r--r-- | drivers/net/phy/linkmode.c | 95 | ||||
-rw-r--r-- | drivers/net/phy/phy-c45.c | 5 | ||||
-rw-r--r-- | drivers/net/phy/phy-core.c | 2 | ||||
-rw-r--r-- | drivers/net/phy/phy_device.c | 48 | ||||
-rw-r--r-- | drivers/net/phy/phylink.c | 162 |
9 files changed, 405 insertions, 88 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 9dabe03a668c..edb1cb8a228e 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -326,8 +326,8 @@ config BROADCOM_PHY BCM5481, BCM54810 and BCM5482 PHYs. config BCM84881_PHY - bool "Broadcom BCM84881 PHY" - depends on PHYLIB=y + tristate "Broadcom BCM84881 PHY" + depends on PHYLIB ---help--- Support the Broadcom BCM84881 PHY. diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index fe5badf13b65..d523fd5670e4 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -1,7 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for Linux PHY drivers and MDIO bus drivers -libphy-y := phy.o phy-c45.o phy-core.o phy_device.o +libphy-y := phy.o phy-c45.o phy-core.o phy_device.o \ + linkmode.o mdio-bus-y += mdio_bus.o mdio_device.o ifdef CONFIG_MDIO_DEVICE diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index a62229a8b1a4..ae4873f2f86e 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -194,7 +194,8 @@ static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev) /* Abort if we are using an untested phy. */ if (BRCM_PHY_MODEL(phydev) != PHY_ID_BCM57780 && BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610 && - BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610M) + BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610M && + BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54810) return; val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3); @@ -272,10 +273,7 @@ static int bcm54xx_config_init(struct phy_device *phydev) (phydev->dev_flags & PHY_BRCM_CLEAR_RGMII_MODE)) bcm_phy_write_shadow(phydev, BCM54XX_SHD_RGMII_MODE, 0); - if ((phydev->dev_flags & PHY_BRCM_RX_REFCLK_UNUSED) || - (phydev->dev_flags & PHY_BRCM_DIS_TXCRXC_NOENRGY) || - (phydev->dev_flags & PHY_BRCM_AUTO_PWRDWN_ENABLE)) - bcm54xx_adjust_rxrefclk(phydev); + bcm54xx_adjust_rxrefclk(phydev); if (BRCM_PHY_MODEL(phydev) == PHY_ID_BCM54210E) { err = bcm54210e_config_init(phydev); @@ -315,6 +313,20 @@ static int bcm54xx_config_init(struct phy_device *phydev) return 0; } +static int bcm54xx_resume(struct phy_device *phydev) +{ + int ret; + + /* Writes to register other than BMCR would be ignored + * unless we clear the PDOWN bit first + */ + ret = genphy_resume(phydev); + if (ret < 0) + return ret; + + return bcm54xx_config_init(phydev); +} + static int bcm5482_config_init(struct phy_device *phydev) { int err, reg; @@ -708,6 +720,8 @@ static struct phy_driver broadcom_drivers[] = { .config_aneg = bcm5481_config_aneg, .ack_interrupt = bcm_phy_ack_intr, .config_intr = bcm_phy_config_intr, + .suspend = genphy_suspend, + .resume = bcm54xx_resume, }, { .phy_id = PHY_ID_BCM5482, .phy_id_mask = 0xfffffff0, diff --git a/drivers/net/phy/dp83867.c b/drivers/net/phy/dp83867.c index 967f57ed0b65..13f7f2d5a2ea 100644 --- a/drivers/net/phy/dp83867.c +++ b/drivers/net/phy/dp83867.c @@ -14,6 +14,7 @@ #include <linux/delay.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> +#include <linux/bitfield.h> #include <dt-bindings/net/ti-dp83867.h> @@ -21,6 +22,7 @@ #define DP83867_DEVADDR 0x1f #define MII_DP83867_PHYCTRL 0x10 +#define MII_DP83867_PHYSTS 0x11 #define MII_DP83867_MICR 0x12 #define MII_DP83867_ISR 0x13 #define DP83867_CFG2 0x14 @@ -118,6 +120,24 @@ #define DP83867_IO_MUX_CFG_CLK_O_SEL_MASK (0x1f << 8) #define DP83867_IO_MUX_CFG_CLK_O_SEL_SHIFT 8 +/* PHY STS bits */ +#define DP83867_PHYSTS_1000 BIT(15) +#define DP83867_PHYSTS_100 BIT(14) +#define DP83867_PHYSTS_DUPLEX BIT(13) +#define DP83867_PHYSTS_LINK BIT(10) + +/* CFG2 bits */ +#define DP83867_DOWNSHIFT_EN (BIT(8) | BIT(9)) +#define DP83867_DOWNSHIFT_ATTEMPT_MASK (BIT(10) | BIT(11)) +#define DP83867_DOWNSHIFT_1_COUNT_VAL 0 +#define DP83867_DOWNSHIFT_2_COUNT_VAL 1 +#define DP83867_DOWNSHIFT_4_COUNT_VAL 2 +#define DP83867_DOWNSHIFT_8_COUNT_VAL 3 +#define DP83867_DOWNSHIFT_1_COUNT 1 +#define DP83867_DOWNSHIFT_2_COUNT 2 +#define DP83867_DOWNSHIFT_4_COUNT 4 +#define DP83867_DOWNSHIFT_8_COUNT 8 + /* CFG3 bits */ #define DP83867_CFG3_INT_OE BIT(7) #define DP83867_CFG3_ROBUST_AUTO_MDIX BIT(9) @@ -287,6 +307,126 @@ static int dp83867_config_intr(struct phy_device *phydev) return phy_write(phydev, MII_DP83867_MICR, micr_status); } +static int dp83867_read_status(struct phy_device *phydev) +{ + int status = phy_read(phydev, MII_DP83867_PHYSTS); + int ret; + + ret = genphy_read_status(phydev); + if (ret) + return ret; + + if (status < 0) + return status; + + if (status & DP83867_PHYSTS_DUPLEX) + phydev->duplex = DUPLEX_FULL; + else + phydev->duplex = DUPLEX_HALF; + + if (status & DP83867_PHYSTS_1000) + phydev->speed = SPEED_1000; + else if (status & DP83867_PHYSTS_100) + phydev->speed = SPEED_100; + else + phydev->speed = SPEED_10; + + return 0; +} + +static int dp83867_get_downshift(struct phy_device *phydev, u8 *data) +{ + int val, cnt, enable, count; + + val = phy_read(phydev, DP83867_CFG2); + if (val < 0) + return val; + + enable = FIELD_GET(DP83867_DOWNSHIFT_EN, val); + cnt = FIELD_GET(DP83867_DOWNSHIFT_ATTEMPT_MASK, val); + + switch (cnt) { + case DP83867_DOWNSHIFT_1_COUNT_VAL: + count = DP83867_DOWNSHIFT_1_COUNT; + break; + case DP83867_DOWNSHIFT_2_COUNT_VAL: + count = DP83867_DOWNSHIFT_2_COUNT; + break; + case DP83867_DOWNSHIFT_4_COUNT_VAL: + count = DP83867_DOWNSHIFT_4_COUNT; + break; + case DP83867_DOWNSHIFT_8_COUNT_VAL: + count = DP83867_DOWNSHIFT_8_COUNT; + break; + default: + return -EINVAL; + }; + + *data = enable ? count : DOWNSHIFT_DEV_DISABLE; + + return 0; +} + +static int dp83867_set_downshift(struct phy_device *phydev, u8 cnt) +{ + int val, count; + + if (cnt > DP83867_DOWNSHIFT_8_COUNT) + return -E2BIG; + + if (!cnt) + return phy_clear_bits(phydev, DP83867_CFG2, + DP83867_DOWNSHIFT_EN); + + switch (cnt) { + case DP83867_DOWNSHIFT_1_COUNT: + count = DP83867_DOWNSHIFT_1_COUNT_VAL; + break; + case DP83867_DOWNSHIFT_2_COUNT: + count = DP83867_DOWNSHIFT_2_COUNT_VAL; + break; + case DP83867_DOWNSHIFT_4_COUNT: + count = DP83867_DOWNSHIFT_4_COUNT_VAL; + break; + case DP83867_DOWNSHIFT_8_COUNT: + count = DP83867_DOWNSHIFT_8_COUNT_VAL; + break; + default: + phydev_err(phydev, + "Downshift count must be 1, 2, 4 or 8\n"); + return -EINVAL; + }; + + val = DP83867_DOWNSHIFT_EN; + val |= FIELD_PREP(DP83867_DOWNSHIFT_ATTEMPT_MASK, count); + + return phy_modify(phydev, DP83867_CFG2, + DP83867_DOWNSHIFT_EN | DP83867_DOWNSHIFT_ATTEMPT_MASK, + val); +} + +static int dp83867_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return dp83867_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int dp83867_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return dp83867_set_downshift(phydev, *(const u8 *)data); + default: + return -EOPNOTSUPP; + } +} + static int dp83867_config_port_mirroring(struct phy_device *phydev) { struct dp83867_private *dp83867 = @@ -467,6 +607,12 @@ static int dp83867_config_init(struct phy_device *phydev) int ret, val, bs; u16 delay; + /* Force speed optimization for the PHY even if it strapped */ + ret = phy_modify(phydev, DP83867_CFG2, DP83867_DOWNSHIFT_EN, + DP83867_DOWNSHIFT_EN); + if (ret) + return ret; + ret = dp83867_verify_rgmii_cfg(phydev); if (ret) return ret; @@ -655,6 +801,10 @@ static struct phy_driver dp83867_driver[] = { .config_init = dp83867_config_init, .soft_reset = dp83867_phy_reset, + .read_status = dp83867_read_status, + .get_tunable = dp83867_get_tunable, + .set_tunable = dp83867_set_tunable, + .get_wol = dp83867_get_wol, .set_wol = dp83867_set_wol, diff --git a/drivers/net/phy/linkmode.c b/drivers/net/phy/linkmode.c new file mode 100644 index 000000000000..f60560fe3499 --- /dev/null +++ b/drivers/net/phy/linkmode.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0+ +#include <linux/linkmode.h> + +/** + * linkmode_resolve_pause - resolve the allowable pause modes + * @local_adv: local advertisement in ethtool format + * @partner_adv: partner advertisement in ethtool format + * @tx_pause: pointer to bool to indicate whether transmit pause should be + * enabled. + * @rx_pause: pointer to bool to indicate whether receive pause should be + * enabled. + * + * Flow control is resolved according to our and the link partners + * advertisements using the following drawn from the 802.3 specs: + * Local device Link partner + * Pause AsymDir Pause AsymDir Result + * 0 X 0 X Disabled + * 0 1 1 0 Disabled + * 0 1 1 1 TX + * 1 0 0 X Disabled + * 1 X 1 X TX+RX + * 1 1 0 1 RX + */ +void linkmode_resolve_pause(const unsigned long *local_adv, + const unsigned long *partner_adv, + bool *tx_pause, bool *rx_pause) +{ + __ETHTOOL_DECLARE_LINK_MODE_MASK(m); + + linkmode_and(m, local_adv, partner_adv); + if (linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, m)) { + *tx_pause = true; + *rx_pause = true; + } else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, m)) { + *tx_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, + partner_adv); + *rx_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT, + local_adv); + } else { + *tx_pause = false; + *rx_pause = false; + } +} +EXPORT_SYMBOL_GPL(linkmode_resolve_pause); + +/** + * linkmode_set_pause - set the pause mode advertisement + * @advertisement: advertisement in ethtool format + * @tx: boolean from ethtool struct ethtool_pauseparam tx_pause member + * @rx: boolean from ethtool struct ethtool_pauseparam rx_pause member + * + * Configure the advertised Pause and Asym_Pause bits according to the + * capabilities of provided in @tx and @rx. + * + * We convert as follows: + * tx rx Pause AsymDir + * 0 0 0 0 + * 0 1 1 1 + * 1 0 0 1 + * 1 1 1 0 + * + * Note: this translation from ethtool tx/rx notation to the advertisement + * is actually very problematical. Here are some examples: + * + * For tx=0 rx=1, meaning transmit is unsupported, receive is supported: + * + * Local device Link partner + * Pause AsymDir Pause AsymDir Result + * 1 1 1 0 TX + RX - but we have no TX support. + * 1 1 0 1 Only this gives RX only + * + * For tx=1 rx=1, meaning we have the capability to transmit and receive + * pause frames: + * + * Local device Link partner + * Pause AsymDir Pause AsymDir Result + * 1 0 0 1 Disabled - but since we do support tx and rx, + * this should resolve to RX only. + * + * Hence, asking for: + * rx=1 tx=0 gives Pause+AsymDir advertisement, but we may end up + * resolving to tx+rx pause or only rx pause depending on + * the partners advertisement. + * rx=0 tx=1 gives AsymDir only, which will only give tx pause if + * the partners advertisement allows it. + * rx=1 tx=1 gives Pause only, which will only allow tx+rx pause + * if the other end also advertises Pause. + */ +void linkmode_set_pause(unsigned long *advertisement, bool tx, bool rx) +{ + linkmode_mod_bit(ETHTOOL_LINK_MODE_Pause_BIT, advertisement, rx); + linkmode_mod_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, advertisement, + rx ^ tx); +} +EXPORT_SYMBOL_GPL(linkmode_set_pause); diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index a1caeee12236..bceb0dcdecbd 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -239,9 +239,10 @@ int genphy_c45_read_link(struct phy_device *phydev) /* The link state is latched low so that momentary link * drops can be detected. Do not double-read the status - * in polling mode to detect such short link drops. + * in polling mode to detect such short link drops except + * the link was already down. */ - if (!phy_polling_mode(phydev)) { + if (!phy_polling_mode(phydev) || !phydev->link) { val = phy_read_mmd(phydev, devad, MDIO_STAT1); if (val < 0) return val; diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index a4d2d59fceca..e083e7a76ada 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -8,7 +8,7 @@ const char *phy_speed_to_str(int speed) { - BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 74, + BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 75, "Enum ethtool_link_mode_bit_indices and phylib are out of sync. " "If a speed or mode has been added please update phy_speed_to_str " "and the PHY settings array.\n"); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 6a5056e0ae77..be0129231c2a 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1930,9 +1930,10 @@ int genphy_update_link(struct phy_device *phydev) /* The link state is latched low so that momentary link * drops can be detected. Do not double-read the status - * in polling mode to detect such short link drops. + * in polling mode to detect such short link drops except + * the link was already down. */ - if (!phy_polling_mode(phydev)) { + if (!phy_polling_mode(phydev) || !phydev->link) { status = phy_read(phydev, MII_BMSR); if (status < 0) return status; @@ -2361,22 +2362,7 @@ void phy_set_asym_pause(struct phy_device *phydev, bool rx, bool tx) __ETHTOOL_DECLARE_LINK_MODE_MASK(oldadv); linkmode_copy(oldadv, phydev->advertising); - - linkmode_clear_bit(ETHTOOL_LINK_MODE_Pause_BIT, - phydev->advertising); - linkmode_clear_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, - phydev->advertising); - - if (rx) { - linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, - phydev->advertising); - linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, - phydev->advertising); - } - - if (tx) - linkmode_change_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, - phydev->advertising); + linkmode_set_pause(phydev->advertising, tx, rx); if (!linkmode_equal(oldadv, phydev->advertising) && phydev->autoneg) @@ -2409,6 +2395,32 @@ bool phy_validate_pause(struct phy_device *phydev, } EXPORT_SYMBOL(phy_validate_pause); +/** + * phy_get_pause - resolve negotiated pause modes + * @phydev: phy_device struct + * @tx_pause: pointer to bool to indicate whether transmit pause should be + * enabled. + * @rx_pause: pointer to bool to indicate whether receive pause should be + * enabled. + * + * Resolve and return the flow control modes according to the negotiation + * result. This includes checking that we are operating in full duplex mode. + * See linkmode_resolve_pause() for further details. + */ +void phy_get_pause(struct phy_device *phydev, bool *tx_pause, bool *rx_pause) +{ + if (phydev->duplex != DUPLEX_FULL) { + *tx_pause = false; + *rx_pause = false; + return; + } + + return linkmode_resolve_pause(phydev->advertising, + phydev->lp_advertising, + tx_pause, rx_pause); +} +EXPORT_SYMBOL(phy_get_pause); + static bool phy_drv_supports_irq(struct phy_driver *phydrv) { return phydrv->config_intr && phydrv->ack_interrupt; diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 70b9a143db84..2899fbe699ab 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -181,9 +181,11 @@ static int phylink_parse_fixedlink(struct phylink *pl, /* We treat the "pause" and "asym-pause" terminology as * defining the link partner's ability. */ if (fwnode_property_read_bool(fixed_node, "pause")) - pl->link_config.pause |= MLO_PAUSE_SYM; + __set_bit(ETHTOOL_LINK_MODE_Pause_BIT, + pl->link_config.lp_advertising); if (fwnode_property_read_bool(fixed_node, "asym-pause")) - pl->link_config.pause |= MLO_PAUSE_ASYM; + __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, + pl->link_config.lp_advertising); if (ret == 0) { desc = fwnode_gpiod_get_index(fixed_node, "link", 0, @@ -215,9 +217,11 @@ static int phylink_parse_fixedlink(struct phylink *pl, DUPLEX_FULL : DUPLEX_HALF; pl->link_config.speed = prop[2]; if (prop[3]) - pl->link_config.pause |= MLO_PAUSE_SYM; + __set_bit(ETHTOOL_LINK_MODE_Pause_BIT, + pl->link_config.lp_advertising); if (prop[4]) - pl->link_config.pause |= MLO_PAUSE_ASYM; + __set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, + pl->link_config.lp_advertising); } } @@ -339,6 +343,34 @@ static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode) return 0; } +static void phylink_apply_manual_flow(struct phylink *pl, + struct phylink_link_state *state) +{ + /* If autoneg is disabled, pause AN is also disabled */ + if (!state->an_enabled) + state->pause &= ~MLO_PAUSE_AN; + + /* Manual configuration of pause modes */ + if (!(pl->link_config.pause & MLO_PAUSE_AN)) + state->pause = pl->link_config.pause; +} + +static void phylink_resolve_flow(struct phylink_link_state *state) +{ + bool tx_pause, rx_pause; + + state->pause = MLO_PAUSE_NONE; + if (state->duplex == DUPLEX_FULL) { + linkmode_resolve_pause(state->advertising, + state->lp_advertising, + &tx_pause, &rx_pause); + if (tx_pause) + state->pause |= MLO_PAUSE_TX; + if (rx_pause) + state->pause |= MLO_PAUSE_RX; + } +} + static void phylink_mac_config(struct phylink *pl, const struct phylink_link_state *state) { @@ -387,49 +419,45 @@ static void phylink_mac_pcs_get_state(struct phylink *pl, /* The fixed state is... fixed except for the link state, * which may be determined by a GPIO or a callback. */ -static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state) +static void phylink_get_fixed_state(struct phylink *pl, + struct phylink_link_state *state) { *state = pl->link_config; if (pl->get_fixed_state) pl->get_fixed_state(pl->netdev, state); else if (pl->link_gpio) state->link = !!gpiod_get_value_cansleep(pl->link_gpio); + + phylink_resolve_flow(state); } -/* Flow control is resolved according to our and the link partners - * advertisements using the following drawn from the 802.3 specs: - * Local device Link partner - * Pause AsymDir Pause AsymDir Result - * 1 X 1 X TX+RX - * 0 1 1 1 TX - * 1 1 0 1 RX - */ -static void phylink_resolve_flow(struct phylink *pl, - struct phylink_link_state *state) +static void phylink_mac_initial_config(struct phylink *pl) { - int new_pause = 0; + struct phylink_link_state link_state; - if (pl->link_config.pause & MLO_PAUSE_AN) { - int pause = 0; + switch (pl->cur_link_an_mode) { + case MLO_AN_PHY: + link_state = pl->phy_state; + break; - if (phylink_test(pl->link_config.advertising, Pause)) - pause |= MLO_PAUSE_SYM; - if (phylink_test(pl->link_config.advertising, Asym_Pause)) - pause |= MLO_PAUSE_ASYM; + case MLO_AN_FIXED: + phylink_get_fixed_state(pl, &link_state); + break; - pause &= state->pause; + case MLO_AN_INBAND: + link_state = pl->link_config; + if (link_state.interface == PHY_INTERFACE_MODE_SGMII) + link_state.pause = MLO_PAUSE_NONE; + break; - if (pause & MLO_PAUSE_SYM) - new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX; - else if (pause & MLO_PAUSE_ASYM) - new_pause = state->pause & MLO_PAUSE_SYM ? - MLO_PAUSE_TX : MLO_PAUSE_RX; - } else { - new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK; + default: /* can't happen */ + return; } - state->pause &= ~MLO_PAUSE_TXRX_MASK; - state->pause |= new_pause; + link_state.link = false; + + phylink_apply_manual_flow(pl, &link_state); + phylink_mac_config(pl, &link_state); } static const char *phylink_pause_to_str(int pause) @@ -493,7 +521,7 @@ static void phylink_resolve(struct work_struct *w) switch (pl->cur_link_an_mode) { case MLO_AN_PHY: link_state = pl->phy_state; - phylink_resolve_flow(pl, &link_state); + phylink_apply_manual_flow(pl, &link_state); phylink_mac_config_up(pl, &link_state); break; @@ -515,9 +543,9 @@ static void phylink_resolve(struct work_struct *w) link_state.interface = pl->phy_state.interface; /* If we have a PHY, we need to update with - * the pause mode bits. */ - link_state.pause |= pl->phy_state.pause; - phylink_resolve_flow(pl, &link_state); + * the PHY flow control bits. */ + link_state.pause = pl->phy_state.pause; + phylink_apply_manual_flow(pl, &link_state); phylink_mac_config(pl, &link_state); } break; @@ -705,15 +733,18 @@ static void phylink_phy_change(struct phy_device *phydev, bool up, bool do_carrier) { struct phylink *pl = phydev->phylink; + bool tx_pause, rx_pause; + + phy_get_pause(phydev, &tx_pause, &rx_pause); mutex_lock(&pl->state_mutex); pl->phy_state.speed = phydev->speed; pl->phy_state.duplex = phydev->duplex; pl->phy_state.pause = MLO_PAUSE_NONE; - if (phydev->pause) - pl->phy_state.pause |= MLO_PAUSE_SYM; - if (phydev->asym_pause) - pl->phy_state.pause |= MLO_PAUSE_ASYM; + if (tx_pause) + pl->phy_state.pause |= MLO_PAUSE_TX; + if (rx_pause) + pl->phy_state.pause |= MLO_PAUSE_RX; pl->phy_state.interface = phydev->interface; pl->phy_state.link = up; mutex_unlock(&pl->state_mutex); @@ -777,6 +808,9 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, mutex_lock(&pl->state_mutex); pl->phydev = phy; pl->phy_state.interface = interface; + pl->phy_state.pause = MLO_PAUSE_NONE; + pl->phy_state.speed = SPEED_UNKNOWN; + pl->phy_state.duplex = DUPLEX_UNKNOWN; linkmode_copy(pl->supported, supported); linkmode_copy(pl->link_config.advertising, config.advertising); @@ -1006,8 +1040,7 @@ void phylink_start(struct phylink *pl) * a fixed-link to start with the correct parameters, and also * ensures that we set the appropriate advertisement for Serdes links. */ - phylink_resolve_flow(pl, &pl->link_config); - phylink_mac_config(pl, &pl->link_config); + phylink_mac_initial_config(pl); /* Restart autonegotiation if using 802.3z to ensure that the link * parameters are properly negotiated. This is necessary for DSA @@ -1373,6 +1406,9 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, ASSERT_RTNL(); + if (pl->cur_link_an_mode == MLO_AN_FIXED) + return -EOPNOTSUPP; + if (!phylink_test(pl->supported, Pause) && !phylink_test(pl->supported, Asym_Pause)) return -EOPNOTSUPP; @@ -1381,8 +1417,8 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, !pause->autoneg && pause->rx_pause != pause->tx_pause) return -EINVAL; - config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK); - + mutex_lock(&pl->state_mutex); + config->pause = 0; if (pause->autoneg) config->pause |= MLO_PAUSE_AN; if (pause->rx_pause) @@ -1390,6 +1426,22 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, if (pause->tx_pause) config->pause |= MLO_PAUSE_TX; + /* + * See the comments for linkmode_set_pause(), wrt the deficiencies + * with the current implementation. A solution to this issue would + * be: + * ethtool Local device + * rx tx Pause AsymDir + * 0 0 0 0 + * 1 0 1 1 + * 0 1 0 1 + * 1 1 1 1 + * and then use the ethtool rx/tx enablement status to mask the + * rx/tx pause resolution. + */ + linkmode_set_pause(config->advertising, pause->tx_pause, + pause->rx_pause); + /* If we have a PHY, phylib will call our link state function if the * mode has changed, which will trigger a resolve and update the MAC * configuration. @@ -1399,19 +1451,10 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, pause->tx_pause); } else if (!test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) { - switch (pl->cur_link_an_mode) { - case MLO_AN_FIXED: - /* Should we allow fixed links to change against the config? */ - phylink_resolve_flow(pl, config); - phylink_mac_config(pl, config); - break; - - case MLO_AN_INBAND: - phylink_mac_config(pl, config); - phylink_mac_an_restart(pl); - break; - } + phylink_mac_config(pl, &pl->link_config); + phylink_mac_an_restart(pl); } + mutex_unlock(&pl->state_mutex); return 0; } @@ -1503,13 +1546,14 @@ static int phylink_mii_emul_read(unsigned int reg, struct phylink_link_state *state) { struct fixed_phy_status fs; + unsigned long *lpa = state->lp_advertising; int val; fs.link = state->link; fs.speed = state->speed; fs.duplex = state->duplex; - fs.pause = state->pause & MLO_PAUSE_SYM; - fs.asym_pause = state->pause & MLO_PAUSE_ASYM; + fs.pause = test_bit(ETHTOOL_LINK_MODE_Pause_BIT, lpa); + fs.asym_pause = test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, lpa); val = swphy_read_reg(reg, &fs); if (reg == MII_BMSR) { @@ -1814,7 +1858,7 @@ static int phylink_sfp_config(struct phylink *pl, u8 mode, if (changed && !test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state)) - phylink_mac_config(pl, &pl->link_config); + phylink_mac_initial_config(pl); return ret; } |