summaryrefslogtreecommitdiff
path: root/drivers/net/phy/phylink.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/phy/phylink.c')
-rw-r--r--drivers/net/phy/phylink.c506
1 files changed, 421 insertions, 85 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index ed0b4ccaa6a6..8a3728d423e7 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -85,7 +85,11 @@ struct phylink {
bool sfp_may_have_phy;
DECLARE_PHY_INTERFACE_MASK(sfp_interfaces);
__ETHTOOL_DECLARE_LINK_MODE_MASK(sfp_support);
+ u8 sfp_link_an_mode;
u8 sfp_port;
+
+ struct eee_config eee_cfg;
+ bool eee_active;
};
#define phylink_printk(level, pl, fmt, ...) \
@@ -647,7 +651,7 @@ static void phylink_validate_mask_caps(unsigned long *supported,
linkmode_and(state->advertising, state->advertising, mask);
}
-static int phylink_validate_mac_and_pcs(struct phylink *pl,
+static int phylink_validate_mac_and_pcs(struct phylink *pl, unsigned int mode,
unsigned long *supported,
struct phylink_link_state *state)
{
@@ -678,7 +682,8 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl,
/* Validate the link parameters with the PCS */
if (pcs->ops->pcs_validate) {
- ret = pcs->ops->pcs_validate(pcs, supported, state);
+ ret = pcs->ops->pcs_validate(pcs, mode, supported,
+ state);
if (ret < 0 || phylink_is_empty_linkmode(supported))
return -EINVAL;
@@ -703,6 +708,7 @@ static int phylink_validate_mac_and_pcs(struct phylink *pl,
}
static void phylink_validate_one(struct phylink *pl, struct phy_device *phy,
+ unsigned int mode,
const unsigned long *supported,
const struct phylink_link_state *state,
phy_interface_t interface,
@@ -720,7 +726,8 @@ static void phylink_validate_one(struct phylink *pl, struct phy_device *phy,
if (phy)
tmp_state.rate_matching = phy_get_rate_matching(phy, interface);
- if (!phylink_validate_mac_and_pcs(pl, tmp_supported, &tmp_state)) {
+ if (!phylink_validate_mac_and_pcs(pl, mode, tmp_supported,
+ &tmp_state)) {
phylink_dbg(pl, " interface %u (%s) rate match %s supports %*pbl\n",
interface, phy_modes(interface),
phy_rate_matching_to_str(tmp_state.rate_matching),
@@ -733,6 +740,7 @@ static void phylink_validate_one(struct phylink *pl, struct phy_device *phy,
}
static int phylink_validate_mask(struct phylink *pl, struct phy_device *phy,
+ unsigned int mode,
unsigned long *supported,
struct phylink_link_state *state,
const unsigned long *interfaces)
@@ -742,7 +750,7 @@ static int phylink_validate_mask(struct phylink *pl, struct phy_device *phy,
int interface;
for_each_set_bit(interface, interfaces, PHY_INTERFACE_MODE_MAX)
- phylink_validate_one(pl, phy, supported, state, interface,
+ phylink_validate_one(pl, phy, mode, supported, state, interface,
all_s, all_adv);
linkmode_copy(supported, all_s);
@@ -751,19 +759,20 @@ static int phylink_validate_mask(struct phylink *pl, struct phy_device *phy,
return phylink_is_empty_linkmode(supported) ? -EINVAL : 0;
}
-static int phylink_validate(struct phylink *pl, unsigned long *supported,
+static int phylink_validate(struct phylink *pl, unsigned int mode,
+ unsigned long *supported,
struct phylink_link_state *state)
{
const unsigned long *interfaces = pl->config->supported_interfaces;
if (state->interface == PHY_INTERFACE_MODE_NA)
- return phylink_validate_mask(pl, NULL, supported, state,
+ return phylink_validate_mask(pl, NULL, mode, supported, state,
interfaces);
if (!test_bit(state->interface, interfaces))
return -EINVAL;
- return phylink_validate_mac_and_pcs(pl, supported, state);
+ return phylink_validate_mac_and_pcs(pl, mode, supported, state);
}
static int phylink_parse_fixedlink(struct phylink *pl,
@@ -841,7 +850,7 @@ static int phylink_parse_fixedlink(struct phylink *pl,
linkmode_fill(pl->supported);
linkmode_copy(pl->link_config.advertising, pl->supported);
- phylink_validate(pl, pl->supported, &pl->link_config);
+ phylink_validate(pl, MLO_AN_FIXED, pl->supported, &pl->link_config);
pause = phylink_test(pl->supported, Pause);
asym_pause = phylink_test(pl->supported, Asym_Pause);
@@ -893,7 +902,7 @@ static int phylink_parse_mode(struct phylink *pl,
if ((fwnode_property_read_string(fwnode, "managed", &managed) == 0 &&
strcmp(managed, "in-band-status") == 0) ||
pl->config->ovr_an_inband) {
- if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
+ if (phylink_mode_fixed(pl->cfg_link_an_mode)) {
phylink_err(pl,
"can't use both fixed-link and in-band-status\n");
return -EINVAL;
@@ -939,7 +948,8 @@ static int phylink_parse_mode(struct phylink *pl,
linkmode_copy(pl->link_config.advertising, pl->supported);
- if (phylink_validate(pl, pl->supported, &pl->link_config)) {
+ if (phylink_validate(pl, pl->cfg_link_an_mode, pl->supported,
+ &pl->link_config)) {
phylink_err(pl,
"failed to validate link configuration for in-band status\n");
return -EINVAL;
@@ -977,6 +987,15 @@ static void phylink_resolve_an_pause(struct phylink_link_state *state)
}
}
+static unsigned int phylink_pcs_query_inband(struct phylink_pcs *pcs,
+ phy_interface_t interface)
+{
+ if (pcs && pcs->ops->pcs_query_inband)
+ return pcs->ops->pcs_query_inband(pcs, interface);
+
+ return 0;
+}
+
static void phylink_pcs_pre_config(struct phylink_pcs *pcs,
phy_interface_t interface)
{
@@ -1032,13 +1051,14 @@ static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
static void phylink_pcs_poll_stop(struct phylink *pl)
{
- if (pl->cfg_link_an_mode == MLO_AN_INBAND)
+ if (phylink_mode_inband(pl->cfg_link_an_mode))
del_timer(&pl->link_poll);
}
static void phylink_pcs_poll_start(struct phylink *pl)
{
- if (pl->pcs && pl->pcs->poll && pl->cfg_link_an_mode == MLO_AN_INBAND)
+ if (pl->pcs && pl->pcs->poll &&
+ phylink_mode_inband(pl->cfg_link_an_mode))
mod_timer(&pl->link_poll, jiffies + HZ);
}
@@ -1076,6 +1096,8 @@ static void phylink_pcs_an_restart(struct phylink *pl)
/**
* phylink_pcs_neg_mode() - helper to determine PCS inband mode
+ * @pl: a pointer to a &struct phylink returned from phylink_create()
+ * @pcs: a pointer to &struct phylink_pcs
* @mode: one of %MLO_AN_FIXED, %MLO_AN_PHY, %MLO_AN_INBAND.
* @interface: interface mode to be used
* @advertising: adertisement ethtool link mode mask
@@ -1093,11 +1115,19 @@ static void phylink_pcs_an_restart(struct phylink *pl)
* Note: this is for cases where the PCS itself is involved in negotiation
* (e.g. Clause 37, SGMII and similar) not Clause 73.
*/
-static unsigned int phylink_pcs_neg_mode(unsigned int mode,
+static unsigned int phylink_pcs_neg_mode(struct phylink *pl,
+ struct phylink_pcs *pcs,
+ unsigned int mode,
phy_interface_t interface,
const unsigned long *advertising)
{
+ unsigned int phy_link_mode = 0;
+ unsigned int pcs_link_mode;
unsigned int neg_mode;
+ enum {
+ INBAND_CISCO_SGMII,
+ INBAND_8023Z,
+ } type;
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
@@ -1109,10 +1139,7 @@ static unsigned int phylink_pcs_neg_mode(unsigned int mode,
* inband communication. Note: there exist PHYs that run
* with SGMII but do not send the inband data.
*/
- if (!phylink_autoneg_inband(mode))
- neg_mode = PHYLINK_PCS_NEG_OUTBAND;
- else
- neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+ type = INBAND_CISCO_SGMII;
break;
case PHY_INTERFACE_MODE_1000BASEX:
@@ -1123,20 +1150,92 @@ static unsigned int phylink_pcs_neg_mode(unsigned int mode,
* as well, but drivers may not support this, so may
* need to override this.
*/
- if (!phylink_autoneg_inband(mode))
- neg_mode = PHYLINK_PCS_NEG_OUTBAND;
- else if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
- advertising))
- neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
- else
- neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
+ type = INBAND_8023Z;
break;
default:
- neg_mode = PHYLINK_PCS_NEG_NONE;
- break;
+ return PHYLINK_PCS_NEG_NONE;
+ }
+
+ pcs_link_mode = phylink_pcs_query_inband(pcs, interface);
+ if (pl->phydev)
+ phy_link_mode = phy_query_inband(pl->phydev, interface);
+
+ if (!phylink_autoneg_inband(mode)) {
+ /* If either the PCS or PHY requires inband to be enabled,
+ * this is an invalid configuration. Provide a diagnostic
+ * message for this case, but don't try to force the issue.
+ */
+ if ((pcs_link_mode | phy_link_mode) & LINK_INBAND_REQUIRED) {
+ const char *s1, *s2, *s3, *empty = "";
+
+ s1 = s2 = s3 = empty;
+ if (pcs_link_mode & LINK_INBAND_REQUIRED)
+ s1 = "PCS";
+
+ if (phy_link_mode & LINK_INBAND_REQUIRED)
+ s3 = "PHY";
+
+ if (s1 != empty && s3 != empty)
+ s2 = " and ";
+
+ phylink_warn(pl,
+ "firmware wants %s mode, but %s%s%s requires inband\n",
+ phylink_an_mode_str(mode),
+ s1, s2, s3);
+ }
+ return PHYLINK_PCS_NEG_OUTBAND;
}
+ /* For SGMII modes, which are designed to be used with PHYs, we
+ * try to use inband mode where-ever possible.
+ */
+ if (type == INBAND_CISCO_SGMII) {
+ /* If the PCS or PHY can not provide inband, then use
+ * out of band.
+ */
+ if (pcs_link_mode == LINK_INBAND_VALID ||
+ phy_link_mode == LINK_INBAND_VALID) {
+ /* If either require inband, then we have a conflict. */
+ return PHYLINK_PCS_NEG_OUTBAND;
+ }
+
+ /* Otherwise, use inband as this is what was requested.
+ * This covers the case where the supported inband modes
+ * have not been provided, or the PCS and PHY report that
+ * they support inband.
+ */
+ return PHYLINK_PCS_NEG_INBAND_ENABLED;
+ }
+
+ /* For 802.3z, handle the PHY present vs absent cases separately */
+ if (!pl->phydev) {
+ /* PHY absent: always use inband but inband may be disabled */
+ /* If the PCS doesn't support inband, then inband must be
+ * disabled.
+ */
+ if (pcs_link_mode == LINK_INBAND_VALID)
+ return PHYLINK_PCS_NEG_INBAND_DISABLED;
+
+ /* If the PCS requires inband, then inband must always be
+ * enabled.
+ */
+ if (pcs_link_mode & LINK_INBAND_REQUIRED)
+ return PHYLINK_PCS_NEG_INBAND_ENABLED;
+
+ /* For the possible case, fall through to the "legacy" code. */
+ } else {
+ /* PHY present, inband mode depends on the capabilities
+ * of both.
+ */
+ }
+
+ /* Legacy, so determine inband depending on the advertising bit */
+ if (linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising))
+ neg_mode = PHYLINK_PCS_NEG_INBAND_ENABLED;
+ else
+ neg_mode = PHYLINK_PCS_NEG_INBAND_DISABLED;
+
return neg_mode;
}
@@ -1151,10 +1250,6 @@ static void phylink_major_config(struct phylink *pl, bool restart,
phylink_dbg(pl, "major config %s\n", phy_modes(state->interface));
- pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
- state->interface,
- state->advertising);
-
if (pl->using_mac_select_pcs) {
pcs = pl->mac_ops->mac_select_pcs(pl->config, state->interface);
if (IS_ERR(pcs)) {
@@ -1167,6 +1262,11 @@ static void phylink_major_config(struct phylink *pl, bool restart,
pcs_changed = pcs && pl->pcs != pcs;
}
+ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pcs,
+ pl->cur_link_an_mode,
+ state->interface,
+ state->advertising);
+
phylink_pcs_poll_stop(pl);
if (pl->mac_ops->mac_prepare) {
@@ -1257,9 +1357,10 @@ static int phylink_change_inband_advert(struct phylink *pl)
pl->link_config.pause);
/* Recompute the PCS neg mode */
- pl->pcs_neg_mode = phylink_pcs_neg_mode(pl->cur_link_an_mode,
- pl->link_config.interface,
- pl->link_config.advertising);
+ pl->pcs_neg_mode = phylink_pcs_neg_mode(pl, pl->pcs,
+ pl->cur_link_an_mode,
+ pl->link_config.interface,
+ pl->link_config.advertising);
neg_mode = pl->cur_link_an_mode;
if (pl->pcs->neg_mode)
@@ -1365,6 +1466,75 @@ static const char *phylink_pause_to_str(int pause)
}
}
+static void phylink_disable_tx_lpi(struct phylink *pl)
+{
+ phylink_dbg(pl, "disabling tx_lpi\n");
+
+ if (pl->mac_ops->mac_disable_tx_lpi)
+ pl->mac_ops->mac_disable_tx_lpi(pl->config);
+}
+
+static void phylink_enable_tx_lpi(struct phylink *pl)
+{
+ phylink_dbg(pl, "enabling tx_lpi, timer %uus\n",
+ pl->eee_cfg.tx_lpi_timer);
+
+ if (pl->mac_ops->mac_enable_tx_lpi)
+ pl->mac_ops->mac_enable_tx_lpi(pl->config,
+ pl->eee_cfg.tx_lpi_timer);
+}
+
+static bool phylink_eee_is_active(struct phylink *pl)
+{
+ return phylink_init_eee(pl, pl->config->eee_clk_stop_enable) >= 0;
+}
+
+static void phylink_deactivate_eee(struct phylink *pl)
+{
+ phylink_dbg(pl, "deactivating EEE, was %sactive\n",
+ pl->eee_active ? "" : "in");
+
+ if (pl->eee_active) {
+ pl->eee_active = false;
+ phylink_disable_tx_lpi(pl);
+ }
+}
+
+static void phylink_activate_eee(struct phylink *pl)
+{
+ pl->eee_active = phylink_eee_is_active(pl);
+
+ phylink_dbg(pl, "can LPI, EEE enabled, %sactive\n",
+ pl->eee_active ? "" : "in");
+
+ if (pl->eee_active)
+ phylink_enable_tx_lpi(pl);
+}
+
+/* Determine whether the MAC has new EEE support. We detect this by checking
+ * for the two new methods being present, but for DSA it will populate these
+ * anyway, so also check that lpi_capabilities is non-zero.
+ */
+static bool phylink_mac_supports_eee(struct phylink *pl)
+{
+ return pl->mac_ops->mac_disable_tx_lpi &&
+ pl->mac_ops->mac_enable_tx_lpi &&
+ pl->config->lpi_capabilities;
+}
+
+static void phylink_phy_restrict_eee(struct phylink *pl, struct phy_device *phy)
+{
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(eee_supported);
+
+ /* Convert the MAC's LPI capabilities to linkmodes */
+ linkmode_zero(eee_supported);
+ phylink_caps_to_linkmodes(eee_supported, pl->config->lpi_capabilities);
+
+ /* Mask out EEE modes that are not supported */
+ linkmode_and(phy->supported_eee, phy->supported_eee, eee_supported);
+ linkmode_and(phy->advertising_eee, phy->advertising_eee, eee_supported);
+}
+
static void phylink_link_up(struct phylink *pl,
struct phylink_link_state link_state)
{
@@ -1411,6 +1581,9 @@ static void phylink_link_up(struct phylink *pl,
pl->cur_interface, speed, duplex,
!!(link_state.pause & MLO_PAUSE_TX), rx_pause);
+ if (eeecfg_mac_can_tx_lpi(&pl->eee_cfg))
+ phylink_activate_eee(pl);
+
if (ndev)
netif_carrier_on(ndev);
@@ -1427,25 +1600,29 @@ static void phylink_link_down(struct phylink *pl)
if (ndev)
netif_carrier_off(ndev);
+
+ phylink_deactivate_eee(pl);
+
pl->mac_ops->mac_link_down(pl->config, pl->cur_link_an_mode,
pl->cur_interface);
phylink_info(pl, "Link is Down\n");
}
+static bool phylink_link_is_up(struct phylink *pl)
+{
+ return pl->netdev ? netif_carrier_ok(pl->netdev) : pl->old_link_state;
+}
+
static void phylink_resolve(struct work_struct *w)
{
struct phylink *pl = container_of(w, struct phylink, resolve);
struct phylink_link_state link_state;
- struct net_device *ndev = pl->netdev;
bool mac_config = false;
bool retrigger = false;
bool cur_link_state;
mutex_lock(&pl->state_mutex);
- if (pl->netdev)
- cur_link_state = netif_carrier_ok(ndev);
- else
- cur_link_state = pl->old_link_state;
+ cur_link_state = phylink_link_is_up(pl);
if (pl->phylink_disable_state) {
pl->mac_link_dropped = false;
@@ -1681,7 +1858,10 @@ struct phylink *phylink_create(struct phylink_config *config,
linkmode_fill(pl->supported);
linkmode_copy(pl->link_config.advertising, pl->supported);
- phylink_validate(pl, pl->supported, &pl->link_config);
+ phylink_validate(pl, MLO_AN_FIXED, pl->supported, &pl->link_config);
+
+ /* Set the default EEE configuration */
+ pl->eee_cfg = pl->config->eee;
ret = phylink_parse_mode(pl, fwnode);
if (ret < 0) {
@@ -1689,7 +1869,7 @@ struct phylink *phylink_create(struct phylink_config *config,
return ERR_PTR(ret);
}
- if (pl->cfg_link_an_mode == MLO_AN_FIXED) {
+ if (phylink_mode_fixed(pl->cfg_link_an_mode)) {
ret = phylink_parse_fixedlink(pl, fwnode);
if (ret < 0) {
kfree(pl);
@@ -1783,6 +1963,9 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy,
struct phylink_link_state *state)
{
DECLARE_PHY_INTERFACE_MASK(interfaces);
+ unsigned int mode;
+
+ mode = pl->cfg_link_an_mode;
/* If the PHY provides a bitmap of the interfaces it will be using
* depending on the negotiated media speeds, use this to validate
@@ -1819,10 +2002,13 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy,
phy->possible_interfaces,
(int)PHY_INTERFACE_MODE_MAX, interfaces);
- return phylink_validate_mask(pl, phy, supported, state,
+ return phylink_validate_mask(pl, phy, mode, supported, state,
interfaces);
}
+ phylink_dbg(pl, "PHY %s doesn't supply possible interfaces\n",
+ phydev_name(phy));
+
/* Check whether we would use rate matching for the proposed interface
* mode.
*/
@@ -1845,7 +2031,7 @@ static int phylink_validate_phy(struct phylink *pl, struct phy_device *phy,
state->interface != PHY_INTERFACE_MODE_USXGMII)
state->interface = PHY_INTERFACE_MODE_NA;
- return phylink_validate(pl, supported, state);
+ return phylink_validate(pl, mode, supported, state);
}
static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
@@ -1902,6 +2088,13 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
/* Restrict the phy advertisement according to the MAC support. */
linkmode_copy(phy->advertising, config.advertising);
+
+ /* If the MAC supports phylink managed EEE, restrict the EEE
+ * advertisement according to the MAC's LPI capabilities.
+ */
+ if (phylink_mac_supports_eee(pl))
+ phylink_phy_restrict_eee(pl, phy);
+
mutex_unlock(&pl->state_mutex);
mutex_unlock(&phy->lock);
@@ -1923,8 +2116,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy,
phy_interface_t interface)
{
- if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED ||
- (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+ if (WARN_ON(phylink_mode_fixed(pl->cfg_link_an_mode) ||
+ (phylink_mode_inband(pl->cfg_link_an_mode) &&
phy_interface_mode_is_8023z(interface) && !pl->sfp_bus)))
return -EINVAL;
@@ -2010,14 +2203,14 @@ int phylink_fwnode_phy_connect(struct phylink *pl,
int ret;
/* Fixed links and 802.3z are handled without needing a PHY */
- if (pl->cfg_link_an_mode == MLO_AN_FIXED ||
- (pl->cfg_link_an_mode == MLO_AN_INBAND &&
+ if (phylink_mode_fixed(pl->cfg_link_an_mode) ||
+ (phylink_mode_inband(pl->cfg_link_an_mode) &&
phy_interface_mode_is_8023z(pl->link_interface)))
return 0;
phy_fwnode = fwnode_get_phy_node(fwnode);
if (IS_ERR(phy_fwnode)) {
- if (pl->cfg_link_an_mode == MLO_AN_PHY)
+ if (phylink_mode_phy(pl->cfg_link_an_mode))
return -ENODEV;
return 0;
}
@@ -2164,7 +2357,7 @@ void phylink_start(struct phylink *pl)
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_STOPPED);
- if (pl->cfg_link_an_mode == MLO_AN_FIXED && pl->link_gpio) {
+ if (phylink_mode_fixed(pl->cfg_link_an_mode) && pl->link_gpio) {
int irq = gpiod_to_irq(pl->link_gpio);
if (irq > 0) {
@@ -2431,6 +2624,39 @@ int phylink_ethtool_ksettings_get(struct phylink *pl,
}
EXPORT_SYMBOL_GPL(phylink_ethtool_ksettings_get);
+static int phylink_query_inband(struct phylink *pl, phy_interface_t interface)
+{
+ struct phylink_pcs *pcs;
+
+ if (!pl->using_mac_select_pcs)
+ return 0;
+
+ pcs = pl->mac_ops->mac_select_pcs(pl->config, interface);
+ if (!pcs)
+ return 0;
+
+ return phylink_pcs_query_inband(pcs, interface);
+}
+
+static bool phylink_validate_pcs_an(struct phylink *pl,
+ phy_interface_t interface, bool an_state)
+{
+ int link_inband = phylink_query_inband(pl, interface);
+
+ /* If the PCS doesn't implement inband support, be permissive. */
+ if (!(link_inband & LINK_INBAND_VALID))
+ return true;
+
+ /* If the PCS can configure the use of inband, allow either state */
+ if (link_inband & LINK_INBAND_POSSIBLE)
+ return true;
+
+ /* Otherwise, require the AN state to be fixed according to the
+ * required flag.
+ */
+ return an_state == !!(link_inband & LINK_INBAND_REQUIRED);
+}
+
/**
* phylink_ethtool_ksettings_set() - set the link settings
* @pl: a pointer to a &struct phylink returned from phylink_create()
@@ -2492,7 +2718,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
/* If we have a fixed link, refuse to change link parameters.
* If the link parameters match, accept them but do nothing.
*/
- if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+ if (phylink_mode_fixed(pl->cur_link_an_mode)) {
if (s->speed != pl->link_config.speed ||
s->duplex != pl->link_config.duplex)
return -EINVAL;
@@ -2508,7 +2734,7 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
* is our default case) but do not allow the advertisement to
* be changed. If the advertisement matches, simply return.
*/
- if (pl->cur_link_an_mode == MLO_AN_FIXED) {
+ if (phylink_mode_fixed(pl->cur_link_an_mode)) {
if (!linkmode_equal(config.advertising,
pl->link_config.advertising))
return -EINVAL;
@@ -2546,7 +2772,8 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
/* Revalidate with the selected interface */
linkmode_copy(support, pl->supported);
- if (phylink_validate(pl, support, &config)) {
+ if (phylink_validate(pl, pl->cur_link_an_mode, support,
+ &config)) {
phylink_err(pl, "validation of %s/%s with support %*pb failed\n",
phylink_an_mode_str(pl->cur_link_an_mode),
phy_modes(config.interface),
@@ -2556,7 +2783,8 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
} else {
/* Validate without changing the current supported mask. */
linkmode_copy(support, pl->supported);
- if (phylink_validate(pl, support, &config))
+ if (phylink_validate(pl, pl->cur_link_an_mode, support,
+ &config))
return -EINVAL;
}
@@ -2566,6 +2794,14 @@ int phylink_ethtool_ksettings_set(struct phylink *pl,
phylink_is_empty_linkmode(config.advertising))
return -EINVAL;
+ /* Validate the autonegotiation state. We don't have a PHY in this
+ * situation, so the PCS is the media-facing entity.
+ */
+ if (!phylink_validate_pcs_an(pl, config.interface,
+ linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ config.advertising)))
+ return -EINVAL;
+
mutex_lock(&pl->state_mutex);
pl->link_config.speed = config.speed;
pl->link_config.duplex = config.duplex;
@@ -2648,7 +2884,7 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl,
ASSERT_RTNL();
- if (pl->cur_link_an_mode == MLO_AN_FIXED)
+ if (phylink_mode_fixed(pl->cur_link_an_mode))
return -EOPNOTSUPP;
if (!phylink_test(pl->supported, Pause) &&
@@ -2756,6 +2992,8 @@ int phylink_init_eee(struct phylink *pl, bool clk_stop_enable)
if (pl->phydev)
ret = phy_init_eee(pl->phydev, clk_stop_enable);
+ else if (pl->sfp_bus && phylink_mac_supports_eee(pl))
+ ret = 0;
return ret;
}
@@ -2772,8 +3010,19 @@ int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee)
ASSERT_RTNL();
- if (pl->phydev)
+ if (pl->phydev) {
ret = phy_ethtool_get_eee(pl->phydev, eee);
+ } else if (pl->sfp_bus && phylink_mac_supports_eee(pl)) {
+ /* For optical SFPs, ensure that eee->supported is nonzero. */
+ eee->supported = SUPPORTED_FIBRE;
+ ret = 0;
+ }
+
+ if (!ret && phylink_mac_supports_eee(pl)) {
+ /* Overwrite phylib's interpretation of configuration */
+ eeecfg_to_eee(&pl->eee_cfg, eee);
+ eee->eee_active = pl->eee_active;
+ }
return ret;
}
@@ -2787,11 +3036,52 @@ EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee);
int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee)
{
int ret = -EOPNOTSUPP;
+ bool mac_eee;
ASSERT_RTNL();
+ mac_eee = phylink_mac_supports_eee(pl);
+
+ phylink_dbg(pl, "mac %s phylink EEE%s, adv 0x%08x, LPI%s timer %uus\n",
+ mac_eee ? "supports" : "does not support",
+ eee->eee_enabled ? ", enabled" : "", eee->advertised,
+ eee->tx_lpi_enabled ? " enabled" : "", eee->tx_lpi_timer);
+
+ /* Clamp the LPI timer maximum value */
+ if (mac_eee && eee->tx_lpi_timer > pl->config->lpi_timer_limit_us) {
+ eee->tx_lpi_timer = pl->config->lpi_timer_limit_us;
+ phylink_dbg(pl, "LPI timer limited to %uus\n",
+ eee->tx_lpi_timer);
+ }
+
if (pl->phydev)
ret = phy_ethtool_set_eee(pl->phydev, eee);
+ else if (pl->sfp_bus && phylink_mac_supports_eee(pl))
+ ret = 0;
+
+ if (!ret && mac_eee) {
+ bool can_lpi, old_can_lpi;
+
+ mutex_lock(&pl->state_mutex);
+ old_can_lpi = eeecfg_mac_can_tx_lpi(&pl->eee_cfg);
+ eee_to_eeecfg(eee, &pl->eee_cfg);
+ can_lpi = eeecfg_mac_can_tx_lpi(&pl->eee_cfg);
+
+ phylink_dbg(pl, "can_lpi %u -> %u\n", old_can_lpi, can_lpi);
+
+ /* If the link is up, and the configuration changes the
+ * LPI permissive state, deal with the change at the MAC.
+ */
+ if (phylink_link_is_up(pl) && old_can_lpi != can_lpi) {
+ phylink_dbg(pl, "link is up, lpi changed\n");
+ if (can_lpi)
+ phylink_activate_eee(pl);
+ else
+ phylink_deactivate_eee(pl);
+ }
+
+ mutex_unlock(&pl->state_mutex);
+ }
return ret;
}
@@ -3144,8 +3434,7 @@ static void phylink_sfp_set_config(struct phylink *pl, u8 mode,
phylink_mac_initial_config(pl, false);
}
-static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
- struct phy_device *phy)
+static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy)
{
__ETHTOOL_DECLARE_LINK_MODE_MASK(support1);
__ETHTOOL_DECLARE_LINK_MODE_MASK(support);
@@ -3163,7 +3452,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
config.pause = MLO_PAUSE_AN;
/* Ignore errors if we're expecting a PHY to attach later */
- ret = phylink_validate(pl, support, &config);
+ ret = phylink_validate(pl, pl->sfp_link_an_mode, support, &config);
if (ret) {
phylink_err(pl, "validation with support %*pb failed: %pe\n",
__ETHTOOL_LINK_MODE_MASK_NBITS, support,
@@ -3181,11 +3470,11 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
config.interface = iface;
linkmode_copy(support1, support);
- ret = phylink_validate(pl, support1, &config);
+ ret = phylink_validate(pl, pl->sfp_link_an_mode, support1, &config);
if (ret) {
phylink_err(pl,
"validation of %s/%s with support %*pb failed: %pe\n",
- phylink_an_mode_str(mode),
+ phylink_an_mode_str(pl->sfp_link_an_mode),
phy_modes(config.interface),
__ETHTOOL_LINK_MODE_MASK_NBITS, support,
ERR_PTR(ret));
@@ -3194,7 +3483,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, u8 mode,
pl->link_port = pl->sfp_port;
- phylink_sfp_set_config(pl, mode, support, &config);
+ phylink_sfp_set_config(pl, pl->sfp_link_an_mode, support, &config);
return 0;
}
@@ -3233,8 +3522,8 @@ static int phylink_sfp_config_optical(struct phylink *pl)
/* For all the interfaces that are supported, reduce the sfp_support
* mask to only those link modes that can be supported.
*/
- ret = phylink_validate_mask(pl, NULL, pl->sfp_support, &config,
- interfaces);
+ ret = phylink_validate_mask(pl, NULL, MLO_AN_INBAND, pl->sfp_support,
+ &config, interfaces);
if (ret) {
phylink_err(pl, "unsupported SFP module: validation with support %*pb failed\n",
__ETHTOOL_LINK_MODE_MASK_NBITS, support);
@@ -3250,10 +3539,17 @@ static int phylink_sfp_config_optical(struct phylink *pl)
phylink_dbg(pl, "optical SFP: chosen %s interface\n",
phy_modes(interface));
+ if (!phylink_validate_pcs_an(pl, interface,
+ linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT,
+ config.advertising))) {
+ phylink_err(pl, "autoneg setting not compatible with PCS");
+ return -EINVAL;
+ }
+
config.interface = interface;
/* Ignore errors if we're expecting a PHY to attach later */
- ret = phylink_validate(pl, support, &config);
+ ret = phylink_validate(pl, MLO_AN_INBAND, support, &config);
if (ret) {
phylink_err(pl, "validation with support %*pb failed: %pe\n",
__ETHTOOL_LINK_MODE_MASK_NBITS, support,
@@ -3279,6 +3575,7 @@ static int phylink_sfp_module_insert(void *upstream,
phy_interface_zero(pl->sfp_interfaces);
sfp_parse_support(pl->sfp_bus, id, pl->sfp_support, pl->sfp_interfaces);
pl->sfp_port = sfp_parse_port(pl->sfp_bus, id, pl->sfp_support);
+ pl->sfp_link_an_mode = MLO_AN_INBAND;
/* If this module may have a PHY connecting later, defer until later */
pl->sfp_may_have_phy = sfp_may_have_phy(pl->sfp_bus, id);
@@ -3334,21 +3631,12 @@ static void phylink_sfp_link_up(void *upstream)
phylink_enable_and_run_resolve(pl, PHYLINK_DISABLE_LINK);
}
-/* The Broadcom BCM84881 in the Methode DM7052 is unable to provide a SGMII
- * or 802.3z control word, so inband will not work.
- */
-static bool phylink_phy_no_inband(struct phy_device *phy)
-{
- return phy->is_c45 && phy_id_compare(phy->c45_ids.device_ids[1],
- 0xae025150, 0xfffffff0);
-}
-
static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
{
+ DECLARE_PHY_INTERFACE_MASK(interfaces);
struct phylink *pl = upstream;
phy_interface_t interface;
- u8 mode;
- int ret;
+ int link_inband, ret;
/*
* This is the new way of dealing with flow control for PHYs,
@@ -3359,21 +3647,63 @@ static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
*/
phy_support_asym_pause(phy);
- if (phylink_phy_no_inband(phy))
- mode = MLO_AN_PHY;
- else
- mode = MLO_AN_INBAND;
-
/* Set the PHY's host supported interfaces */
phy_interface_and(phy->host_interfaces, phylink_sfp_interfaces,
pl->config->supported_interfaces);
- /* Do the initial configuration */
- ret = phylink_sfp_config_phy(pl, mode, phy);
- if (ret < 0)
- return ret;
+ if (phy_interface_empty(phy->supported_interfaces)) {
+ phylink_dbg(pl, "copper SFP: PHY provides empty supported_interfaces\n");
+
+ /* Do the initial configuration */
+ ret = phylink_sfp_config_phy(pl, phy);
+ if (ret < 0)
+ return ret;
+ } else {
+ phylink_dbg(pl, "copper SFP: interfaces=[mac=%*pbl, sfp=%*pbl]\n",
+ (int)PHY_INTERFACE_MODE_MAX,
+ pl->config->supported_interfaces,
+ (int)PHY_INTERFACE_MODE_MAX,
+ phy->supported_interfaces);
+
+ phy_interface_and(interfaces, phy->supported_interfaces,
+ pl->config->supported_interfaces);
+ interface = phylink_choose_sfp_interface(pl, interfaces);
+ if (interface == PHY_INTERFACE_MODE_NA) {
+ phylink_err(pl,
+ "selection of interface for PHY failed\n");
+ return -EINVAL;
+ }
+
+ phylink_dbg(pl, "copper SFP: chosen %s interface\n",
+ phy_modes(interface));
+
+ link_inband = phy_query_inband(phy, interface);
+ phylink_dbg(pl, "copper SFP: PHY link in-band modes 0x%x\n",
+ link_inband);
+
+ /* If the link inband is valid but the PHY doesn't support
+ * inband, then we have to use PHY mode. E.g. BCM84881.
+ */
+ if (link_inband == LINK_INBAND_VALID)
+ pl->sfp_link_an_mode = MLO_AN_PHY;
+
+ if (pl->cur_link_an_mode != pl->sfp_link_an_mode ||
+ pl->link_config.interface != interface) {
+ pl->link_config.interface = interface;
+ pl->cur_link_an_mode = pl->sfp_link_an_mode;
+
+ phylink_info(pl, "switched to %s/%s link mode\n",
+ phylink_an_mode_str(pl->sfp_link_an_mode),
+ phy_modes(interface));
+ }
+
+ if (!test_bit(PHYLINK_DISABLE_STOPPED,
+ &pl->phylink_disable_state))
+ phylink_mac_initial_config(pl, false);
+ }
interface = pl->link_config.interface;
+
ret = phylink_attach_phy(pl, phy, interface);
if (ret < 0)
return ret;
@@ -3501,12 +3831,18 @@ static void phylink_decode_sgmii_word(struct phylink_link_state *state,
* @lpa: a 16 bit value which stores the USXGMII auto-negotiation word
*
* Helper for MAC PCS supporting the USXGMII protocol and the auto-negotiation
- * code word. Decode the USXGMII code word and populate the corresponding fields
- * (speed, duplex) into the phylink_link_state structure.
+ * code word. Decode the USXGMII code word and populate the corresponding fields
+ * (speed, duplex) into the phylink_link_state structure. If the code word
+ * indicates link is down, or we are unable to decode it, set the link down.
*/
void phylink_decode_usxgmii_word(struct phylink_link_state *state,
uint16_t lpa)
{
+ if (!(lpa & MDIO_USXGMII_LINK)) {
+ state->link = false;
+ return;
+ }
+
switch (lpa & MDIO_USXGMII_SPD_MASK) {
case MDIO_USXGMII_10:
state->speed = SPEED_10;