Commit 6dbdd4de authored by Vitaly Lifshits's avatar Vitaly Lifshits Committed by Tony Nguyen

e1000e: Workaround for sporadic MDI error on Meteor Lake systems

On some Meteor Lake systems accessing the PHY via the MDIO interface may
result in an MDI error. This issue happens sporadically and in most cases
a second access to the PHY via the MDIO interface results in success.

As a workaround, introduce a retry counter which is set to 3 on Meteor
Lake systems. The driver will only return an error if 3 consecutive PHY
access attempts fail. The retry mechanism is disabled in specific flows,
where MDI errors are expected.

Fixes: cc23f4f0 ("e1000e: Add support for Meteor Lake")
Suggested-by: default avatarNikolay Mushayev <nikolay.mushayev@intel.com>
Co-developed-by: default avatarNir Efrati <nir.efrati@intel.com>
Signed-off-by: default avatarNir Efrati <nir.efrati@intel.com>
Signed-off-by: default avatarVitaly Lifshits <vitaly.lifshits@intel.com>
Tested-by: default avatarNaama Meir <naamax.meir@linux.intel.com>
Signed-off-by: default avatarTony Nguyen <anthony.l.nguyen@intel.com>
parent afbf75e8
...@@ -628,6 +628,7 @@ struct e1000_phy_info { ...@@ -628,6 +628,7 @@ struct e1000_phy_info {
u32 id; u32 id;
u32 reset_delay_us; /* in usec */ u32 reset_delay_us; /* in usec */
u32 revision; u32 revision;
u32 retry_count;
enum e1000_media_type media_type; enum e1000_media_type media_type;
...@@ -644,6 +645,7 @@ struct e1000_phy_info { ...@@ -644,6 +645,7 @@ struct e1000_phy_info {
bool polarity_correction; bool polarity_correction;
bool speed_downgraded; bool speed_downgraded;
bool autoneg_wait_to_complete; bool autoneg_wait_to_complete;
bool retry_enabled;
}; };
struct e1000_nvm_info { struct e1000_nvm_info {
......
...@@ -222,11 +222,18 @@ static bool e1000_phy_is_accessible_pchlan(struct e1000_hw *hw) ...@@ -222,11 +222,18 @@ static bool e1000_phy_is_accessible_pchlan(struct e1000_hw *hw)
if (hw->mac.type >= e1000_pch_lpt) { if (hw->mac.type >= e1000_pch_lpt) {
/* Only unforce SMBus if ME is not active */ /* Only unforce SMBus if ME is not active */
if (!(er32(FWSM) & E1000_ICH_FWSM_FW_VALID)) { if (!(er32(FWSM) & E1000_ICH_FWSM_FW_VALID)) {
/* Switching PHY interface always returns MDI error
* so disable retry mechanism to avoid wasting time
*/
e1000e_disable_phy_retry(hw);
/* Unforce SMBus mode in PHY */ /* Unforce SMBus mode in PHY */
e1e_rphy_locked(hw, CV_SMB_CTRL, &phy_reg); e1e_rphy_locked(hw, CV_SMB_CTRL, &phy_reg);
phy_reg &= ~CV_SMB_CTRL_FORCE_SMBUS; phy_reg &= ~CV_SMB_CTRL_FORCE_SMBUS;
e1e_wphy_locked(hw, CV_SMB_CTRL, phy_reg); e1e_wphy_locked(hw, CV_SMB_CTRL, phy_reg);
e1000e_enable_phy_retry(hw);
/* Unforce SMBus mode in MAC */ /* Unforce SMBus mode in MAC */
mac_reg = er32(CTRL_EXT); mac_reg = er32(CTRL_EXT);
mac_reg &= ~E1000_CTRL_EXT_FORCE_SMBUS; mac_reg &= ~E1000_CTRL_EXT_FORCE_SMBUS;
...@@ -310,6 +317,11 @@ static s32 e1000_init_phy_workarounds_pchlan(struct e1000_hw *hw) ...@@ -310,6 +317,11 @@ static s32 e1000_init_phy_workarounds_pchlan(struct e1000_hw *hw)
goto out; goto out;
} }
/* There is no guarantee that the PHY is accessible at this time
* so disable retry mechanism to avoid wasting time
*/
e1000e_disable_phy_retry(hw);
/* The MAC-PHY interconnect may be in SMBus mode. If the PHY is /* The MAC-PHY interconnect may be in SMBus mode. If the PHY is
* inaccessible and resetting the PHY is not blocked, toggle the * inaccessible and resetting the PHY is not blocked, toggle the
* LANPHYPC Value bit to force the interconnect to PCIe mode. * LANPHYPC Value bit to force the interconnect to PCIe mode.
...@@ -380,6 +392,8 @@ static s32 e1000_init_phy_workarounds_pchlan(struct e1000_hw *hw) ...@@ -380,6 +392,8 @@ static s32 e1000_init_phy_workarounds_pchlan(struct e1000_hw *hw)
break; break;
} }
e1000e_enable_phy_retry(hw);
hw->phy.ops.release(hw); hw->phy.ops.release(hw);
if (!ret_val) { if (!ret_val) {
...@@ -449,6 +463,11 @@ static s32 e1000_init_phy_params_pchlan(struct e1000_hw *hw) ...@@ -449,6 +463,11 @@ static s32 e1000_init_phy_params_pchlan(struct e1000_hw *hw)
phy->id = e1000_phy_unknown; phy->id = e1000_phy_unknown;
if (hw->mac.type == e1000_pch_mtp) {
phy->retry_count = 2;
e1000e_enable_phy_retry(hw);
}
ret_val = e1000_init_phy_workarounds_pchlan(hw); ret_val = e1000_init_phy_workarounds_pchlan(hw);
if (ret_val) if (ret_val)
return ret_val; return ret_val;
...@@ -1146,6 +1165,11 @@ s32 e1000_enable_ulp_lpt_lp(struct e1000_hw *hw, bool to_sx) ...@@ -1146,6 +1165,11 @@ s32 e1000_enable_ulp_lpt_lp(struct e1000_hw *hw, bool to_sx)
if (ret_val) if (ret_val)
goto out; goto out;
/* Switching PHY interface always returns MDI error
* so disable retry mechanism to avoid wasting time
*/
e1000e_disable_phy_retry(hw);
/* Force SMBus mode in PHY */ /* Force SMBus mode in PHY */
ret_val = e1000_read_phy_reg_hv_locked(hw, CV_SMB_CTRL, &phy_reg); ret_val = e1000_read_phy_reg_hv_locked(hw, CV_SMB_CTRL, &phy_reg);
if (ret_val) if (ret_val)
...@@ -1153,6 +1177,8 @@ s32 e1000_enable_ulp_lpt_lp(struct e1000_hw *hw, bool to_sx) ...@@ -1153,6 +1177,8 @@ s32 e1000_enable_ulp_lpt_lp(struct e1000_hw *hw, bool to_sx)
phy_reg |= CV_SMB_CTRL_FORCE_SMBUS; phy_reg |= CV_SMB_CTRL_FORCE_SMBUS;
e1000_write_phy_reg_hv_locked(hw, CV_SMB_CTRL, phy_reg); e1000_write_phy_reg_hv_locked(hw, CV_SMB_CTRL, phy_reg);
e1000e_enable_phy_retry(hw);
/* Force SMBus mode in MAC */ /* Force SMBus mode in MAC */
mac_reg = er32(CTRL_EXT); mac_reg = er32(CTRL_EXT);
mac_reg |= E1000_CTRL_EXT_FORCE_SMBUS; mac_reg |= E1000_CTRL_EXT_FORCE_SMBUS;
...@@ -1313,6 +1339,11 @@ static s32 e1000_disable_ulp_lpt_lp(struct e1000_hw *hw, bool force) ...@@ -1313,6 +1339,11 @@ static s32 e1000_disable_ulp_lpt_lp(struct e1000_hw *hw, bool force)
/* Toggle LANPHYPC Value bit */ /* Toggle LANPHYPC Value bit */
e1000_toggle_lanphypc_pch_lpt(hw); e1000_toggle_lanphypc_pch_lpt(hw);
/* Switching PHY interface always returns MDI error
* so disable retry mechanism to avoid wasting time
*/
e1000e_disable_phy_retry(hw);
/* Unforce SMBus mode in PHY */ /* Unforce SMBus mode in PHY */
ret_val = e1000_read_phy_reg_hv_locked(hw, CV_SMB_CTRL, &phy_reg); ret_val = e1000_read_phy_reg_hv_locked(hw, CV_SMB_CTRL, &phy_reg);
if (ret_val) { if (ret_val) {
...@@ -1333,6 +1364,8 @@ static s32 e1000_disable_ulp_lpt_lp(struct e1000_hw *hw, bool force) ...@@ -1333,6 +1364,8 @@ static s32 e1000_disable_ulp_lpt_lp(struct e1000_hw *hw, bool force)
phy_reg &= ~CV_SMB_CTRL_FORCE_SMBUS; phy_reg &= ~CV_SMB_CTRL_FORCE_SMBUS;
e1000_write_phy_reg_hv_locked(hw, CV_SMB_CTRL, phy_reg); e1000_write_phy_reg_hv_locked(hw, CV_SMB_CTRL, phy_reg);
e1000e_enable_phy_retry(hw);
/* Unforce SMBus mode in MAC */ /* Unforce SMBus mode in MAC */
mac_reg = er32(CTRL_EXT); mac_reg = er32(CTRL_EXT);
mac_reg &= ~E1000_CTRL_EXT_FORCE_SMBUS; mac_reg &= ~E1000_CTRL_EXT_FORCE_SMBUS;
......
...@@ -107,6 +107,16 @@ s32 e1000e_phy_reset_dsp(struct e1000_hw *hw) ...@@ -107,6 +107,16 @@ s32 e1000e_phy_reset_dsp(struct e1000_hw *hw)
return e1e_wphy(hw, M88E1000_PHY_GEN_CONTROL, 0); return e1e_wphy(hw, M88E1000_PHY_GEN_CONTROL, 0);
} }
void e1000e_disable_phy_retry(struct e1000_hw *hw)
{
hw->phy.retry_enabled = false;
}
void e1000e_enable_phy_retry(struct e1000_hw *hw)
{
hw->phy.retry_enabled = true;
}
/** /**
* e1000e_read_phy_reg_mdic - Read MDI control register * e1000e_read_phy_reg_mdic - Read MDI control register
* @hw: pointer to the HW structure * @hw: pointer to the HW structure
...@@ -118,55 +128,73 @@ s32 e1000e_phy_reset_dsp(struct e1000_hw *hw) ...@@ -118,55 +128,73 @@ s32 e1000e_phy_reset_dsp(struct e1000_hw *hw)
**/ **/
s32 e1000e_read_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 *data) s32 e1000e_read_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 *data)
{ {
u32 i, mdic = 0, retry_counter, retry_max;
struct e1000_phy_info *phy = &hw->phy; struct e1000_phy_info *phy = &hw->phy;
u32 i, mdic = 0; bool success;
if (offset > MAX_PHY_REG_ADDRESS) { if (offset > MAX_PHY_REG_ADDRESS) {
e_dbg("PHY Address %d is out of range\n", offset); e_dbg("PHY Address %d is out of range\n", offset);
return -E1000_ERR_PARAM; return -E1000_ERR_PARAM;
} }
retry_max = phy->retry_enabled ? phy->retry_count : 0;
/* Set up Op-code, Phy Address, and register offset in the MDI /* Set up Op-code, Phy Address, and register offset in the MDI
* Control register. The MAC will take care of interfacing with the * Control register. The MAC will take care of interfacing with the
* PHY to retrieve the desired data. * PHY to retrieve the desired data.
*/ */
mdic = ((offset << E1000_MDIC_REG_SHIFT) | for (retry_counter = 0; retry_counter <= retry_max; retry_counter++) {
(phy->addr << E1000_MDIC_PHY_SHIFT) | success = true;
(E1000_MDIC_OP_READ));
ew32(MDIC, mdic); mdic = ((offset << E1000_MDIC_REG_SHIFT) |
(phy->addr << E1000_MDIC_PHY_SHIFT) |
(E1000_MDIC_OP_READ));
/* Poll the ready bit to see if the MDI read completed ew32(MDIC, mdic);
* Increasing the time out as testing showed failures with
* the lower time out /* Poll the ready bit to see if the MDI read completed
*/ * Increasing the time out as testing showed failures with
for (i = 0; i < (E1000_GEN_POLL_TIMEOUT * 3); i++) { * the lower time out
udelay(50); */
mdic = er32(MDIC); for (i = 0; i < (E1000_GEN_POLL_TIMEOUT * 3); i++) {
if (mdic & E1000_MDIC_READY) usleep_range(50, 60);
break; mdic = er32(MDIC);
} if (mdic & E1000_MDIC_READY)
if (!(mdic & E1000_MDIC_READY)) { break;
e_dbg("MDI Read PHY Reg Address %d did not complete\n", offset); }
return -E1000_ERR_PHY; if (!(mdic & E1000_MDIC_READY)) {
} e_dbg("MDI Read PHY Reg Address %d did not complete\n",
if (mdic & E1000_MDIC_ERROR) { offset);
e_dbg("MDI Read PHY Reg Address %d Error\n", offset); success = false;
return -E1000_ERR_PHY; }
} if (mdic & E1000_MDIC_ERROR) {
if (FIELD_GET(E1000_MDIC_REG_MASK, mdic) != offset) { e_dbg("MDI Read PHY Reg Address %d Error\n", offset);
e_dbg("MDI Read offset error - requested %d, returned %d\n", success = false;
offset, FIELD_GET(E1000_MDIC_REG_MASK, mdic)); }
return -E1000_ERR_PHY; if (FIELD_GET(E1000_MDIC_REG_MASK, mdic) != offset) {
e_dbg("MDI Read offset error - requested %d, returned %d\n",
offset, FIELD_GET(E1000_MDIC_REG_MASK, mdic));
success = false;
}
/* Allow some time after each MDIC transaction to avoid
* reading duplicate data in the next MDIC transaction.
*/
if (hw->mac.type == e1000_pch2lan)
usleep_range(100, 150);
if (success) {
*data = (u16)mdic;
return 0;
}
if (retry_counter != retry_max) {
e_dbg("Perform retry on PHY transaction...\n");
mdelay(10);
}
} }
*data = (u16)mdic;
/* Allow some time after each MDIC transaction to avoid return -E1000_ERR_PHY;
* reading duplicate data in the next MDIC transaction.
*/
if (hw->mac.type == e1000_pch2lan)
udelay(100);
return 0;
} }
/** /**
...@@ -179,56 +207,72 @@ s32 e1000e_read_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 *data) ...@@ -179,56 +207,72 @@ s32 e1000e_read_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 *data)
**/ **/
s32 e1000e_write_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 data) s32 e1000e_write_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 data)
{ {
u32 i, mdic = 0, retry_counter, retry_max;
struct e1000_phy_info *phy = &hw->phy; struct e1000_phy_info *phy = &hw->phy;
u32 i, mdic = 0; bool success;
if (offset > MAX_PHY_REG_ADDRESS) { if (offset > MAX_PHY_REG_ADDRESS) {
e_dbg("PHY Address %d is out of range\n", offset); e_dbg("PHY Address %d is out of range\n", offset);
return -E1000_ERR_PARAM; return -E1000_ERR_PARAM;
} }
retry_max = phy->retry_enabled ? phy->retry_count : 0;
/* Set up Op-code, Phy Address, and register offset in the MDI /* Set up Op-code, Phy Address, and register offset in the MDI
* Control register. The MAC will take care of interfacing with the * Control register. The MAC will take care of interfacing with the
* PHY to retrieve the desired data. * PHY to retrieve the desired data.
*/ */
mdic = (((u32)data) | for (retry_counter = 0; retry_counter <= retry_max; retry_counter++) {
(offset << E1000_MDIC_REG_SHIFT) | success = true;
(phy->addr << E1000_MDIC_PHY_SHIFT) |
(E1000_MDIC_OP_WRITE));
ew32(MDIC, mdic); mdic = (((u32)data) |
(offset << E1000_MDIC_REG_SHIFT) |
(phy->addr << E1000_MDIC_PHY_SHIFT) |
(E1000_MDIC_OP_WRITE));
/* Poll the ready bit to see if the MDI read completed ew32(MDIC, mdic);
* Increasing the time out as testing showed failures with
* the lower time out
*/
for (i = 0; i < (E1000_GEN_POLL_TIMEOUT * 3); i++) {
udelay(50);
mdic = er32(MDIC);
if (mdic & E1000_MDIC_READY)
break;
}
if (!(mdic & E1000_MDIC_READY)) {
e_dbg("MDI Write PHY Reg Address %d did not complete\n", offset);
return -E1000_ERR_PHY;
}
if (mdic & E1000_MDIC_ERROR) {
e_dbg("MDI Write PHY Red Address %d Error\n", offset);
return -E1000_ERR_PHY;
}
if (FIELD_GET(E1000_MDIC_REG_MASK, mdic) != offset) {
e_dbg("MDI Write offset error - requested %d, returned %d\n",
offset, FIELD_GET(E1000_MDIC_REG_MASK, mdic));
return -E1000_ERR_PHY;
}
/* Allow some time after each MDIC transaction to avoid /* Poll the ready bit to see if the MDI read completed
* reading duplicate data in the next MDIC transaction. * Increasing the time out as testing showed failures with
*/ * the lower time out
if (hw->mac.type == e1000_pch2lan) */
udelay(100); for (i = 0; i < (E1000_GEN_POLL_TIMEOUT * 3); i++) {
usleep_range(50, 60);
mdic = er32(MDIC);
if (mdic & E1000_MDIC_READY)
break;
}
if (!(mdic & E1000_MDIC_READY)) {
e_dbg("MDI Write PHY Reg Address %d did not complete\n",
offset);
success = false;
}
if (mdic & E1000_MDIC_ERROR) {
e_dbg("MDI Write PHY Reg Address %d Error\n", offset);
success = false;
}
if (FIELD_GET(E1000_MDIC_REG_MASK, mdic) != offset) {
e_dbg("MDI Write offset error - requested %d, returned %d\n",
offset, FIELD_GET(E1000_MDIC_REG_MASK, mdic));
success = false;
}
return 0; /* Allow some time after each MDIC transaction to avoid
* reading duplicate data in the next MDIC transaction.
*/
if (hw->mac.type == e1000_pch2lan)
usleep_range(100, 150);
if (success)
return 0;
if (retry_counter != retry_max) {
e_dbg("Perform retry on PHY transaction...\n");
mdelay(10);
}
}
return -E1000_ERR_PHY;
} }
/** /**
......
...@@ -51,6 +51,8 @@ s32 e1000e_read_phy_reg_bm2(struct e1000_hw *hw, u32 offset, u16 *data); ...@@ -51,6 +51,8 @@ s32 e1000e_read_phy_reg_bm2(struct e1000_hw *hw, u32 offset, u16 *data);
s32 e1000e_write_phy_reg_bm2(struct e1000_hw *hw, u32 offset, u16 data); s32 e1000e_write_phy_reg_bm2(struct e1000_hw *hw, u32 offset, u16 data);
void e1000_power_up_phy_copper(struct e1000_hw *hw); void e1000_power_up_phy_copper(struct e1000_hw *hw);
void e1000_power_down_phy_copper(struct e1000_hw *hw); void e1000_power_down_phy_copper(struct e1000_hw *hw);
void e1000e_disable_phy_retry(struct e1000_hw *hw);
void e1000e_enable_phy_retry(struct e1000_hw *hw);
s32 e1000e_read_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 *data); s32 e1000e_read_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 *data);
s32 e1000e_write_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 data); s32 e1000e_write_phy_reg_mdic(struct e1000_hw *hw, u32 offset, u16 data);
s32 e1000_read_phy_reg_hv(struct e1000_hw *hw, u32 offset, u16 *data); s32 e1000_read_phy_reg_hv(struct e1000_hw *hw, u32 offset, u16 *data);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment