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:
Faizal Rahim 2026-05-08 05:47:06 +08:00 committed by Tony Nguyen
parent fa7315482f
commit acb138b823
8 changed files with 218 additions and 49 deletions

View File

@ -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 = {

View File

@ -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 */

View File

@ -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) {

View File

@ -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 {

View File

@ -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");

View File

@ -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;

View File

@ -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;
}

View File

@ -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