aboutsummaryrefslogtreecommitdiffstats
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.c209
1 files changed, 148 insertions, 61 deletions
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 5efdeb59f4b2..d0aaa5cad853 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -71,6 +71,7 @@ struct phylink {
struct mutex state_mutex;
struct phylink_link_state phy_state;
struct work_struct resolve;
+ unsigned int pcs_neg_mode;
bool mac_link_dropped;
bool using_mac_select_pcs;
@@ -156,6 +157,23 @@ static const char *phylink_an_mode_str(unsigned int mode)
return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown";
}
+static unsigned int phylink_interface_signal_rate(phy_interface_t interface)
+{
+ switch (interface) {
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX: /* 1.25Mbd */
+ return 1250;
+ case PHY_INTERFACE_MODE_2500BASEX: /* 3.125Mbd */
+ return 3125;
+ case PHY_INTERFACE_MODE_5GBASER: /* 5.15625Mbd */
+ return 5156;
+ case PHY_INTERFACE_MODE_10GBASER: /* 10.3125Mbd */
+ return 10313;
+ default:
+ return 0;
+ }
+}
+
/**
* phylink_interface_max_speed() - get the maximum speed of a phy interface
* @interface: phy interface mode defined by &typedef phy_interface_t
@@ -695,20 +713,17 @@ static int phylink_validate(struct phylink *pl, unsigned long *supported,
{
const unsigned long *interfaces = pl->config->supported_interfaces;
- if (!phy_interface_empty(interfaces)) {
- if (state->interface == PHY_INTERFACE_MODE_NA)
- return phylink_validate_mask(pl, supported, state,
- interfaces);
+ if (state->interface == PHY_INTERFACE_MODE_NA)
+ return phylink_validate_mask(pl, supported, state, interfaces);
- if (!test_bit(state->interface, interfaces))
- return -EINVAL;
- }
+ if (!test_bit(state->interface, interfaces))
+ return -EINVAL;
return phylink_validate_mac_and_pcs(pl, supported, state);
}
static int phylink_parse_fixedlink(struct phylink *pl,
- struct fwnode_handle *fwnode)
+ const struct fwnode_handle *fwnode)
{
struct fwnode_handle *fixed_node;
bool pause, asym_pause, autoneg;
@@ -819,7 +834,8 @@ static int phylink_parse_fixedlink(struct phylink *pl,
return 0;
}
-static int phylink_parse_mode(struct phylink *pl, struct fwnode_handle *fwnode)
+static int phylink_parse_mode(struct phylink *pl,
+ const struct fwnode_handle *fwnode)
{
struct fwnode_handle *dn;
const char *managed;
@@ -962,11 +978,10 @@ static void phylink_apply_manual_flow(struct phylink *pl,
state->pause = pl->link_config.pause;
}
-static void phylink_resolve_flow(struct phylink_link_state *state)
+static void phylink_resolve_an_pause(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,
@@ -978,6 +993,25 @@ static void phylink_resolve_flow(struct phylink_link_state *state)
}
}
+static int phylink_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
+ const struct phylink_link_state *state,
+ bool permit_pause_to_mac)
+{
+ if (!pcs)
+ return 0;
+
+ return pcs->ops->pcs_config(pcs, neg_mode, state->interface,
+ state->advertising, permit_pause_to_mac);
+}
+
+static void phylink_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
+ phy_interface_t interface, int speed,
+ int duplex)
+{
+ if (pcs && pcs->ops->pcs_link_up)
+ pcs->ops->pcs_link_up(pcs, neg_mode, interface, speed, duplex);
+}
+
static void phylink_pcs_poll_stop(struct phylink *pl)
{
if (pl->cfg_link_an_mode == MLO_AN_INBAND)
@@ -1024,10 +1058,16 @@ static void phylink_major_config(struct phylink *pl, bool restart,
{
struct phylink_pcs *pcs = NULL;
bool pcs_changed = false;
+ unsigned int rate_kbd;
+ unsigned int neg_mode;
int err;
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)) {
@@ -1060,18 +1100,18 @@ static void phylink_major_config(struct phylink *pl, bool restart,
phylink_mac_config(pl, state);
- if (pl->pcs) {
- err = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode,
- state->interface,
- state->advertising,
- !!(pl->link_config.pause &
- MLO_PAUSE_AN));
- if (err < 0)
- phylink_err(pl, "pcs_config failed: %pe\n",
- ERR_PTR(err));
- if (err > 0)
- restart = true;
- }
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs && pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+
+ err = phylink_pcs_config(pl->pcs, neg_mode, state,
+ !!(pl->link_config.pause & MLO_PAUSE_AN));
+ if (err < 0)
+ phylink_err(pl, "pcs_config failed: %pe\n",
+ ERR_PTR(err));
+ else if (err > 0)
+ restart = true;
+
if (restart)
phylink_mac_pcs_an_restart(pl);
@@ -1083,6 +1123,12 @@ static void phylink_major_config(struct phylink *pl, bool restart,
ERR_PTR(err));
}
+ if (pl->sfp_bus) {
+ rate_kbd = phylink_interface_signal_rate(state->interface);
+ if (rate_kbd)
+ sfp_upstream_set_signal_rate(pl->sfp_bus, rate_kbd);
+ }
+
phylink_pcs_poll_start(pl);
}
@@ -1094,6 +1140,7 @@ static void phylink_major_config(struct phylink *pl, bool restart,
*/
static int phylink_change_inband_advert(struct phylink *pl)
{
+ unsigned int neg_mode;
int ret;
if (test_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state))
@@ -1112,15 +1159,21 @@ static int phylink_change_inband_advert(struct phylink *pl)
__ETHTOOL_LINK_MODE_MASK_NBITS, pl->link_config.advertising,
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);
+
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+
/* Modern PCS-based method; update the advert at the PCS, and
* restart negotiation if the pcs_config() helper indicates that
* the programmed advertisement has changed.
*/
- ret = pl->pcs->ops->pcs_config(pl->pcs, pl->cur_link_an_mode,
- pl->link_config.interface,
- pl->link_config.advertising,
- !!(pl->link_config.pause &
- MLO_PAUSE_AN));
+ ret = phylink_pcs_config(pl->pcs, neg_mode, &pl->link_config,
+ !!(pl->link_config.pause & MLO_PAUSE_AN));
if (ret < 0)
return ret;
@@ -1171,7 +1224,8 @@ static void phylink_get_fixed_state(struct phylink *pl,
else if (pl->link_gpio)
state->link = !!gpiod_get_value_cansleep(pl->link_gpio);
- phylink_resolve_flow(state);
+ state->pause = MLO_PAUSE_NONE;
+ phylink_resolve_an_pause(state);
}
static void phylink_mac_initial_config(struct phylink *pl, bool force_restart)
@@ -1221,6 +1275,7 @@ static void phylink_link_up(struct phylink *pl,
struct phylink_link_state link_state)
{
struct net_device *ndev = pl->netdev;
+ unsigned int neg_mode;
int speed, duplex;
bool rx_pause;
@@ -1251,9 +1306,12 @@ static void phylink_link_up(struct phylink *pl,
pl->cur_interface = link_state.interface;
- if (pl->pcs && pl->pcs->ops->pcs_link_up)
- pl->pcs->ops->pcs_link_up(pl->pcs, pl->cur_link_an_mode,
- pl->cur_interface, speed, duplex);
+ neg_mode = pl->cur_link_an_mode;
+ if (pl->pcs && pl->pcs->neg_mode)
+ neg_mode = pl->pcs_neg_mode;
+
+ phylink_pcs_link_up(pl->pcs, neg_mode, pl->cur_interface, speed,
+ duplex);
pl->mac_ops->mac_link_up(pl->config, pl->phydev, pl->cur_link_an_mode,
pl->cur_interface, speed, duplex,
@@ -1441,7 +1499,7 @@ static void phylink_fixed_poll(struct timer_list *t)
static const struct sfp_upstream_ops sfp_phylink_ops;
static int phylink_register_sfp(struct phylink *pl,
- struct fwnode_handle *fwnode)
+ const struct fwnode_handle *fwnode)
{
struct sfp_bus *bus;
int ret;
@@ -1480,7 +1538,7 @@ static int phylink_register_sfp(struct phylink *pl,
* must use IS_ERR() to check for errors from this function.
*/
struct phylink *phylink_create(struct phylink_config *config,
- struct fwnode_handle *fwnode,
+ const struct fwnode_handle *fwnode,
phy_interface_t iface,
const struct phylink_mac_ops *mac_ops)
{
@@ -1488,19 +1546,18 @@ struct phylink *phylink_create(struct phylink_config *config,
struct phylink *pl;
int ret;
- if (mac_ops->mac_select_pcs &&
- mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
- ERR_PTR(-EOPNOTSUPP))
- using_mac_select_pcs = true;
-
/* Validate the supplied configuration */
- if (using_mac_select_pcs &&
- phy_interface_empty(config->supported_interfaces)) {
+ if (phy_interface_empty(config->supported_interfaces)) {
dev_err(config->dev,
- "phylink: error: empty supported_interfaces but mac_select_pcs() method present\n");
+ "phylink: error: empty supported_interfaces\n");
return ERR_PTR(-EINVAL);
}
+ if (mac_ops->mac_select_pcs &&
+ mac_ops->mac_select_pcs(config, PHY_INTERFACE_MODE_NA) !=
+ ERR_PTR(-EOPNOTSUPP))
+ using_mac_select_pcs = true;
+
pl = kzalloc(sizeof(*pl), GFP_KERNEL);
if (!pl)
return ERR_PTR(-ENOMEM);
@@ -1809,7 +1866,7 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect);
* Returns 0 on success or a negative errno.
*/
int phylink_fwnode_phy_connect(struct phylink *pl,
- struct fwnode_handle *fwnode,
+ const struct fwnode_handle *fwnode,
u32 flags)
{
struct fwnode_handle *phy_fwnode;
@@ -3131,8 +3188,8 @@ static void phylink_sfp_link_up(void *upstream)
*/
static bool phylink_phy_no_inband(struct phy_device *phy)
{
- return phy->is_c45 &&
- (phy->c45_ids.device_ids[1] & 0xfffffff0) == 0xae025150;
+ 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)
@@ -3196,10 +3253,48 @@ static const struct sfp_upstream_ops sfp_phylink_ops = {
/* Helpers for MAC drivers */
+static struct {
+ int bit;
+ int speed;
+} phylink_c73_priority_resolution[] = {
+ { ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, SPEED_100000 },
+ { ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, SPEED_100000 },
+ /* 100GBASE-KP4 and 100GBASE-CR10 not supported */
+ { ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, SPEED_40000 },
+ { ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, SPEED_40000 },
+ { ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, SPEED_10000 },
+ { ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT, SPEED_10000 },
+ /* 5GBASE-KR not supported */
+ { ETHTOOL_LINK_MODE_2500baseX_Full_BIT, SPEED_2500 },
+ { ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, SPEED_1000 },
+};
+
+void phylink_resolve_c73(struct phylink_link_state *state)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(phylink_c73_priority_resolution); i++) {
+ int bit = phylink_c73_priority_resolution[i].bit;
+ if (linkmode_test_bit(bit, state->advertising) &&
+ linkmode_test_bit(bit, state->lp_advertising))
+ break;
+ }
+
+ if (i < ARRAY_SIZE(phylink_c73_priority_resolution)) {
+ state->speed = phylink_c73_priority_resolution[i].speed;
+ state->duplex = DUPLEX_FULL;
+ } else {
+ /* negotiation failure */
+ state->link = false;
+ }
+
+ phylink_resolve_an_pause(state);
+}
+EXPORT_SYMBOL_GPL(phylink_resolve_c73);
+
static void phylink_decode_c37_word(struct phylink_link_state *state,
uint16_t config_reg, int speed)
{
- bool tx_pause, rx_pause;
int fd_bit;
if (speed == SPEED_2500)
@@ -3218,13 +3313,7 @@ static void phylink_decode_c37_word(struct phylink_link_state *state,
state->link = false;
}
- 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;
+ phylink_resolve_an_pause(state);
}
static void phylink_decode_sgmii_word(struct phylink_link_state *state,
@@ -3456,18 +3545,19 @@ EXPORT_SYMBOL_GPL(phylink_mii_c22_pcs_encode_advertisement);
/**
* phylink_mii_c22_pcs_config() - configure clause 22 PCS
* @pcs: a pointer to a &struct mdio_device.
- * @mode: link autonegotiation mode
* @interface: the PHY interface mode being configured
* @advertising: the ethtool advertisement mask
+ * @neg_mode: PCS negotiation mode
*
* Configure a Clause 22 PCS PHY with the appropriate negotiation
* parameters for the @mode, @interface and @advertising parameters.
* Returns negative error number on failure, zero if the advertisement
* has not changed, or positive if there is a change.
*/
-int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode,
+int phylink_mii_c22_pcs_config(struct mdio_device *pcs,
phy_interface_t interface,
- const unsigned long *advertising)
+ const unsigned long *advertising,
+ unsigned int neg_mode)
{
bool changed = 0;
u16 bmcr;
@@ -3482,15 +3572,12 @@ int phylink_mii_c22_pcs_config(struct mdio_device *pcs, unsigned int mode,
changed = ret;
}
- /* Ensure ISOLATE bit is disabled */
- if (mode == MLO_AN_INBAND &&
- (interface == PHY_INTERFACE_MODE_SGMII ||
- interface == PHY_INTERFACE_MODE_QSGMII ||
- linkmode_test_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, advertising)))
+ if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
bmcr = BMCR_ANENABLE;
else
bmcr = 0;
+ /* Configure the inband state. Ensure ISOLATE bit is disabled */
ret = mdiodev_modify(pcs, MII_BMCR, BMCR_ANENABLE | BMCR_ISOLATE, bmcr);
if (ret < 0)
return ret;