mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
igc: add support for forcing link speed without autonegotiation
Allow users to force 10/100 Mb/s link speed and duplex via ethtool when autonegotiation is disabled. Previously, the driver rejected these requests with "Force mode currently not supported.". Forcing at 1000 Mb/s and 2500 Mb/s is not supported. Reviewed-by: Looi Hong Aun <hong.aun.looi@intel.com> Signed-off-by: Faizal Rahim <faizal.abdul.rahim@linux.intel.com> Signed-off-by: Khai Wen Tan <khai.wen.tan@linux.intel.com> Reviewed-by: Simon Horman <horms@kernel.org> Reviewed-by: Dima Ruinskiy <dima.ruinskiy@intel.com> Tested-by: Moriya Kadosh <moriyax.kadosh@intel.com> Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
This commit is contained in:
parent
fa7315482f
commit
acb138b823
|
|
@ -114,11 +114,35 @@ static s32 igc_setup_copper_link_base(struct igc_hw *hw)
|
|||
u32 ctrl;
|
||||
|
||||
ctrl = rd32(IGC_CTRL);
|
||||
ctrl |= IGC_CTRL_SLU;
|
||||
ctrl &= ~(IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX);
|
||||
wr32(IGC_CTRL, ctrl);
|
||||
ctrl &= ~(IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX |
|
||||
IGC_CTRL_SPEED_MASK | IGC_CTRL_FD);
|
||||
|
||||
ret_val = igc_setup_copper_link(hw);
|
||||
if (hw->mac.autoneg_enabled) {
|
||||
ctrl |= IGC_CTRL_SLU;
|
||||
wr32(IGC_CTRL, ctrl);
|
||||
ret_val = igc_setup_copper_link(hw);
|
||||
} else {
|
||||
ctrl |= IGC_CTRL_SLU | IGC_CTRL_FRCSPD | IGC_CTRL_FRCDPX;
|
||||
|
||||
switch (hw->mac.forced_speed_duplex) {
|
||||
case IGC_FORCED_10H:
|
||||
ctrl |= IGC_CTRL_SPEED_10;
|
||||
break;
|
||||
case IGC_FORCED_10F:
|
||||
ctrl |= IGC_CTRL_SPEED_10 | IGC_CTRL_FD;
|
||||
break;
|
||||
case IGC_FORCED_100H:
|
||||
ctrl |= IGC_CTRL_SPEED_100;
|
||||
break;
|
||||
case IGC_FORCED_100F:
|
||||
ctrl |= IGC_CTRL_SPEED_100 | IGC_CTRL_FD;
|
||||
break;
|
||||
default:
|
||||
return -IGC_ERR_CONFIG;
|
||||
}
|
||||
wr32(IGC_CTRL, ctrl);
|
||||
ret_val = igc_setup_copper_link(hw);
|
||||
}
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
|
@ -443,6 +467,7 @@ static const struct igc_phy_operations igc_phy_ops_base = {
|
|||
.reset = igc_phy_hw_reset,
|
||||
.read_reg = igc_read_phy_reg_gpy,
|
||||
.write_reg = igc_write_phy_reg_gpy,
|
||||
.force_speed_duplex = igc_force_speed_duplex,
|
||||
};
|
||||
|
||||
const struct igc_info igc_base_info = {
|
||||
|
|
|
|||
|
|
@ -129,10 +129,13 @@
|
|||
#define IGC_ERR_SWFW_SYNC 13
|
||||
|
||||
/* Device Control */
|
||||
#define IGC_CTRL_FD BIT(0) /* Full Duplex */
|
||||
#define IGC_CTRL_RST 0x04000000 /* Global reset */
|
||||
|
||||
#define IGC_CTRL_PHY_RST 0x80000000 /* PHY Reset */
|
||||
#define IGC_CTRL_SLU 0x00000040 /* Set link up (Force Link) */
|
||||
#define IGC_CTRL_SPEED_MASK GENMASK(10, 8)
|
||||
#define IGC_CTRL_SPEED_10 FIELD_PREP(IGC_CTRL_SPEED_MASK, 0)
|
||||
#define IGC_CTRL_SPEED_100 FIELD_PREP(IGC_CTRL_SPEED_MASK, 1)
|
||||
#define IGC_CTRL_FRCSPD 0x00000800 /* Force Speed */
|
||||
#define IGC_CTRL_FRCDPX 0x00001000 /* Force Duplex */
|
||||
#define IGC_CTRL_VME 0x40000000 /* IEEE VLAN mode enable */
|
||||
|
|
@ -673,6 +676,10 @@
|
|||
#define IGC_GEN_POLL_TIMEOUT 1920
|
||||
|
||||
/* PHY Control Register */
|
||||
#define MII_CR_SPEED_MASK (BIT(6) | BIT(13))
|
||||
#define MII_CR_SPEED_10 0x0000 /* SSM=0, SSL=0: 10 Mb/s */
|
||||
#define MII_CR_SPEED_100 BIT(13) /* SSM=0, SSL=1: 100 Mb/s */
|
||||
#define MII_CR_DUPLEX_EN BIT(8) /* 0 = Half Duplex, 1 = Full Duplex */
|
||||
#define MII_CR_RESTART_AUTO_NEG 0x0200 /* Restart auto negotiation */
|
||||
#define MII_CR_POWER_DOWN 0x0800 /* Power down */
|
||||
#define MII_CR_AUTO_NEG_EN 0x1000 /* Auto Neg Enable */
|
||||
|
|
|
|||
|
|
@ -1946,44 +1946,58 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev,
|
|||
ethtool_link_ksettings_add_link_mode(cmd, supported, TP);
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, TP);
|
||||
|
||||
/* advertising link modes */
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, 10baseT_Half);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_10_FULL)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, 10baseT_Full);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_100_HALF)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Half);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_100_FULL)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, 100baseT_Full);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_1000_FULL)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, 1000baseT_Full);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_2500_FULL)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, 2500baseT_Full);
|
||||
|
||||
/* set autoneg settings */
|
||||
ethtool_link_ksettings_add_link_mode(cmd, supported, Autoneg);
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, Autoneg);
|
||||
if (hw->mac.autoneg_enabled) {
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, Autoneg);
|
||||
cmd->base.autoneg = AUTONEG_ENABLE;
|
||||
|
||||
/* Set pause flow control settings */
|
||||
ethtool_link_ksettings_add_link_mode(cmd, supported, Pause);
|
||||
/* advertising link modes only apply when autoneg is on */
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_10_HALF)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
10baseT_Half);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_10_FULL)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
10baseT_Full);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_100_HALF)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
100baseT_Half);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_100_FULL)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
100baseT_Full);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_1000_FULL)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
1000baseT_Full);
|
||||
if (hw->phy.autoneg_advertised & ADVERTISE_2500_FULL)
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
2500baseT_Full);
|
||||
|
||||
switch (hw->fc.requested_mode) {
|
||||
case igc_fc_full:
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause);
|
||||
break;
|
||||
case igc_fc_rx_pause:
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising, Pause);
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
Asym_Pause);
|
||||
break;
|
||||
case igc_fc_tx_pause:
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
Asym_Pause);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
/* Set pause flow control advertising */
|
||||
switch (hw->fc.requested_mode) {
|
||||
case igc_fc_full:
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
Pause);
|
||||
break;
|
||||
case igc_fc_rx_pause:
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
Pause);
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
Asym_Pause);
|
||||
break;
|
||||
case igc_fc_tx_pause:
|
||||
ethtool_link_ksettings_add_link_mode(cmd, advertising,
|
||||
Asym_Pause);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
cmd->base.autoneg = AUTONEG_DISABLE;
|
||||
}
|
||||
|
||||
/* Pause is always supported */
|
||||
ethtool_link_ksettings_add_link_mode(cmd, supported, Pause);
|
||||
|
||||
status = pm_runtime_suspended(&adapter->pdev->dev) ?
|
||||
0 : rd32(IGC_STATUS);
|
||||
|
||||
|
|
@ -2015,7 +2029,6 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev,
|
|||
cmd->base.duplex = DUPLEX_UNKNOWN;
|
||||
}
|
||||
cmd->base.speed = speed;
|
||||
cmd->base.autoneg = AUTONEG_ENABLE;
|
||||
|
||||
/* MDI-X => 2; MDI =>1; Invalid =>0 */
|
||||
if (hw->phy.media_type == igc_media_type_copper)
|
||||
|
|
@ -2032,6 +2045,37 @@ static int igc_ethtool_get_link_ksettings(struct net_device *netdev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* igc_handle_autoneg_disabled - Configure forced speed/duplex settings
|
||||
* @adapter: private driver structure
|
||||
* @speed: requested speed (must be SPEED_10 or SPEED_100)
|
||||
* @duplex: requested duplex
|
||||
*
|
||||
* Records forced speed/duplex when autoneg is disabled.
|
||||
* Caller must validate speed before calling this function.
|
||||
*/
|
||||
static void igc_handle_autoneg_disabled(struct igc_adapter *adapter, u32 speed,
|
||||
u8 duplex)
|
||||
{
|
||||
struct igc_mac_info *mac = &adapter->hw.mac;
|
||||
|
||||
switch (speed) {
|
||||
case SPEED_10:
|
||||
mac->forced_speed_duplex = (duplex == DUPLEX_FULL) ?
|
||||
IGC_FORCED_10F : IGC_FORCED_10H;
|
||||
break;
|
||||
case SPEED_100:
|
||||
mac->forced_speed_duplex = (duplex == DUPLEX_FULL) ?
|
||||
IGC_FORCED_100F : IGC_FORCED_100H;
|
||||
break;
|
||||
default:
|
||||
WARN_ONCE(1, "Unsupported speed %u\n", speed);
|
||||
return;
|
||||
}
|
||||
|
||||
mac->autoneg_enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* igc_handle_autoneg_enabled - Configure autonegotiation advertisement
|
||||
* @adapter: private driver structure
|
||||
|
|
@ -2070,6 +2114,7 @@ static void igc_handle_autoneg_enabled(struct igc_adapter *adapter,
|
|||
10baseT_Half))
|
||||
advertised |= ADVERTISE_10_HALF;
|
||||
|
||||
hw->mac.autoneg_enabled = true;
|
||||
hw->phy.autoneg_advertised = advertised;
|
||||
if (adapter->fc_autoneg)
|
||||
hw->fc.requested_mode = igc_fc_default;
|
||||
|
|
@ -2091,6 +2136,12 @@ igc_ethtool_set_link_ksettings(struct net_device *netdev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (cmd->base.autoneg != AUTONEG_ENABLE &&
|
||||
cmd->base.autoneg != AUTONEG_DISABLE) {
|
||||
netdev_info(dev, "Unsupported autoneg setting\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* MDI setting is only allowed when autoneg enabled because
|
||||
* some hardware doesn't allow MDI setting when speed or
|
||||
* duplex is forced.
|
||||
|
|
@ -2103,14 +2154,25 @@ igc_ethtool_set_link_ksettings(struct net_device *netdev,
|
|||
}
|
||||
}
|
||||
|
||||
if (cmd->base.autoneg == AUTONEG_DISABLE) {
|
||||
if (cmd->base.speed != SPEED_10 && cmd->base.speed != SPEED_100) {
|
||||
netdev_info(dev, "Unsupported speed for forced link\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL) {
|
||||
netdev_info(dev, "Duplex must be half or full for forced link\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
while (test_and_set_bit(__IGC_RESETTING, &adapter->state))
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
if (cmd->base.autoneg == AUTONEG_ENABLE) {
|
||||
if (cmd->base.autoneg == AUTONEG_ENABLE)
|
||||
igc_handle_autoneg_enabled(adapter, cmd);
|
||||
} else {
|
||||
netdev_info(dev, "Force mode currently not supported\n");
|
||||
}
|
||||
else
|
||||
igc_handle_autoneg_disabled(adapter, cmd->base.speed,
|
||||
cmd->base.duplex);
|
||||
|
||||
/* MDI-X => 2; MDI => 1; Auto => 3 */
|
||||
if (cmd->base.eth_tp_mdix_ctrl) {
|
||||
|
|
|
|||
|
|
@ -73,6 +73,13 @@ struct igc_info {
|
|||
|
||||
extern const struct igc_info igc_base_info;
|
||||
|
||||
enum igc_forced_speed_duplex {
|
||||
IGC_FORCED_10H,
|
||||
IGC_FORCED_10F,
|
||||
IGC_FORCED_100H,
|
||||
IGC_FORCED_100F,
|
||||
};
|
||||
|
||||
struct igc_mac_info {
|
||||
struct igc_mac_operations ops;
|
||||
|
||||
|
|
@ -93,6 +100,8 @@ struct igc_mac_info {
|
|||
bool arc_subsystem_valid;
|
||||
|
||||
bool get_link_status;
|
||||
bool autoneg_enabled;
|
||||
enum igc_forced_speed_duplex forced_speed_duplex;
|
||||
};
|
||||
|
||||
struct igc_nvm_operations {
|
||||
|
|
|
|||
|
|
@ -446,6 +446,17 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw)
|
|||
u16 speed, duplex;
|
||||
s32 ret_val = 0;
|
||||
|
||||
/* Without autoneg, flow control capability is not exchanged with the
|
||||
* link partner. IEEE 802.3 prohibits flow control in half-duplex mode.
|
||||
*/
|
||||
if (!hw->mac.autoneg_enabled) {
|
||||
if (hw->mac.forced_speed_duplex == IGC_FORCED_10H ||
|
||||
hw->mac.forced_speed_duplex == IGC_FORCED_100H)
|
||||
hw->fc.current_mode = igc_fc_none;
|
||||
|
||||
goto force_fc;
|
||||
}
|
||||
|
||||
/* In auto-neg, we need to check and see if Auto-Neg has completed,
|
||||
* and if so, how the PHY and link partner has flow control
|
||||
* configured.
|
||||
|
|
@ -607,6 +618,7 @@ s32 igc_config_fc_after_link_up(struct igc_hw *hw)
|
|||
/* Now we call a subroutine to actually force the MAC
|
||||
* controller to use the correct flow control settings.
|
||||
*/
|
||||
force_fc:
|
||||
ret_val = igc_force_mac_fc(hw);
|
||||
if (ret_val) {
|
||||
hw_dbg("Error forcing flow control settings\n");
|
||||
|
|
|
|||
|
|
@ -7298,7 +7298,7 @@ static int igc_probe(struct pci_dev *pdev,
|
|||
/* Initialize link properties that are user-changeable */
|
||||
adapter->fc_autoneg = true;
|
||||
hw->phy.autoneg_advertised = 0xaf;
|
||||
|
||||
hw->mac.autoneg_enabled = true;
|
||||
hw->fc.requested_mode = igc_fc_default;
|
||||
hw->fc.current_mode = igc_fc_default;
|
||||
|
||||
|
|
|
|||
|
|
@ -494,12 +494,20 @@ s32 igc_setup_copper_link(struct igc_hw *hw)
|
|||
s32 ret_val = 0;
|
||||
bool link;
|
||||
|
||||
/* Setup autoneg and flow control advertisement and perform
|
||||
* autonegotiation.
|
||||
*/
|
||||
ret_val = igc_copper_link_autoneg(hw);
|
||||
if (ret_val)
|
||||
goto out;
|
||||
if (hw->mac.autoneg_enabled) {
|
||||
/* Setup autoneg and flow control advertisement and perform
|
||||
* autonegotiation.
|
||||
*/
|
||||
ret_val = igc_copper_link_autoneg(hw);
|
||||
if (ret_val)
|
||||
goto out;
|
||||
} else {
|
||||
ret_val = hw->phy.ops.force_speed_duplex(hw);
|
||||
if (ret_val) {
|
||||
hw_dbg("Error Forcing Speed/Duplex\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check link status. Wait up to 100 microseconds for link to become
|
||||
* valid.
|
||||
|
|
@ -778,3 +786,48 @@ u16 igc_read_phy_fw_version(struct igc_hw *hw)
|
|||
|
||||
return gphy_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* igc_force_speed_duplex - Force PHY speed and duplex settings
|
||||
* @hw: pointer to the HW structure
|
||||
*
|
||||
* Programs the GPY PHY control register to disable autonegotiation
|
||||
* and force the speed/duplex indicated by hw->mac.forced_speed_duplex.
|
||||
*/
|
||||
s32 igc_force_speed_duplex(struct igc_hw *hw)
|
||||
{
|
||||
struct igc_phy_info *phy = &hw->phy;
|
||||
u16 phy_ctrl;
|
||||
s32 ret_val;
|
||||
|
||||
ret_val = phy->ops.read_reg(hw, PHY_CONTROL, &phy_ctrl);
|
||||
if (ret_val)
|
||||
return ret_val;
|
||||
|
||||
phy_ctrl &= ~(MII_CR_SPEED_MASK | MII_CR_DUPLEX_EN |
|
||||
MII_CR_AUTO_NEG_EN | MII_CR_RESTART_AUTO_NEG);
|
||||
|
||||
switch (hw->mac.forced_speed_duplex) {
|
||||
case IGC_FORCED_10H:
|
||||
phy_ctrl |= MII_CR_SPEED_10;
|
||||
break;
|
||||
case IGC_FORCED_10F:
|
||||
phy_ctrl |= MII_CR_SPEED_10 | MII_CR_DUPLEX_EN;
|
||||
break;
|
||||
case IGC_FORCED_100H:
|
||||
phy_ctrl |= MII_CR_SPEED_100;
|
||||
break;
|
||||
case IGC_FORCED_100F:
|
||||
phy_ctrl |= MII_CR_SPEED_100 | MII_CR_DUPLEX_EN;
|
||||
break;
|
||||
default:
|
||||
return -IGC_ERR_CONFIG;
|
||||
}
|
||||
|
||||
ret_val = phy->ops.write_reg(hw, PHY_CONTROL, phy_ctrl);
|
||||
if (ret_val)
|
||||
return ret_val;
|
||||
|
||||
hw->mac.get_link_status = true;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,5 +18,6 @@ void igc_power_down_phy_copper(struct igc_hw *hw);
|
|||
s32 igc_write_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 data);
|
||||
s32 igc_read_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 *data);
|
||||
u16 igc_read_phy_fw_version(struct igc_hw *hw);
|
||||
s32 igc_force_speed_duplex(struct igc_hw *hw);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user