Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

Commit

Permalink
e1000e: Workaround for sporadic MDI error on Meteor Lake systems
Browse files Browse the repository at this point in the history
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: cc23f4f ("e1000e: Add support for Meteor Lake")
Suggested-by: Nikolay Mushayev <nikolay.mushayev@intel.com>
Co-developed-by: Nir Efrati <nir.efrati@intel.com>
Signed-off-by: Nir Efrati <nir.efrati@intel.com>
Signed-off-by: Vitaly Lifshits <vitaly.lifshits@intel.com>
Tested-by: Naama Meir <naamax.meir@linux.intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
  • Loading branch information
vlifshts authored and anguy11 committed Mar 27, 2024
1 parent afbf75e commit 6dbdd4d
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 69 deletions.
2 changes: 2 additions & 0 deletions drivers/net/ethernet/intel/e1000e/hw.h
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ struct e1000_phy_info {
u32 id;
u32 reset_delay_us; /* in usec */
u32 revision;
u32 retry_count;

enum e1000_media_type media_type;

Expand All @@ -644,6 +645,7 @@ struct e1000_phy_info {
bool polarity_correction;
bool speed_downgraded;
bool autoneg_wait_to_complete;
bool retry_enabled;
};

struct e1000_nvm_info {
Expand Down
33 changes: 33 additions & 0 deletions drivers/net/ethernet/intel/e1000e/ich8lan.c
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,18 @@ static bool e1000_phy_is_accessible_pchlan(struct e1000_hw *hw)
if (hw->mac.type >= e1000_pch_lpt) {
/* Only unforce SMBus if ME is not active */
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 */
e1e_rphy_locked(hw, CV_SMB_CTRL, &phy_reg);
phy_reg &= ~CV_SMB_CTRL_FORCE_SMBUS;
e1e_wphy_locked(hw, CV_SMB_CTRL, phy_reg);

e1000e_enable_phy_retry(hw);

/* Unforce SMBus mode in MAC */
mac_reg = er32(CTRL_EXT);
mac_reg &= ~E1000_CTRL_EXT_FORCE_SMBUS;
Expand Down Expand Up @@ -310,6 +317,11 @@ static s32 e1000_init_phy_workarounds_pchlan(struct e1000_hw *hw)
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
* inaccessible and resetting the PHY is not blocked, toggle the
* LANPHYPC Value bit to force the interconnect to PCIe mode.
Expand Down Expand Up @@ -380,6 +392,8 @@ static s32 e1000_init_phy_workarounds_pchlan(struct e1000_hw *hw)
break;
}

e1000e_enable_phy_retry(hw);

hw->phy.ops.release(hw);
if (!ret_val) {

Expand Down Expand Up @@ -449,6 +463,11 @@ static s32 e1000_init_phy_params_pchlan(struct e1000_hw *hw)

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);
if (ret_val)
return ret_val;
Expand Down Expand Up @@ -1146,13 +1165,20 @@ s32 e1000_enable_ulp_lpt_lp(struct e1000_hw *hw, bool to_sx)
if (ret_val)
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 */
ret_val = e1000_read_phy_reg_hv_locked(hw, CV_SMB_CTRL, &phy_reg);
if (ret_val)
goto release;
phy_reg |= CV_SMB_CTRL_FORCE_SMBUS;
e1000_write_phy_reg_hv_locked(hw, CV_SMB_CTRL, phy_reg);

e1000e_enable_phy_retry(hw);

/* Force SMBus mode in MAC */
mac_reg = er32(CTRL_EXT);
mac_reg |= E1000_CTRL_EXT_FORCE_SMBUS;
Expand Down Expand Up @@ -1313,6 +1339,11 @@ static s32 e1000_disable_ulp_lpt_lp(struct e1000_hw *hw, bool force)
/* Toggle LANPHYPC Value bit */
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 */
ret_val = e1000_read_phy_reg_hv_locked(hw, CV_SMB_CTRL, &phy_reg);
if (ret_val) {
Expand All @@ -1333,6 +1364,8 @@ static s32 e1000_disable_ulp_lpt_lp(struct e1000_hw *hw, bool force)
phy_reg &= ~CV_SMB_CTRL_FORCE_SMBUS;
e1000_write_phy_reg_hv_locked(hw, CV_SMB_CTRL, phy_reg);

e1000e_enable_phy_retry(hw);

/* Unforce SMBus mode in MAC */
mac_reg = er32(CTRL_EXT);
mac_reg &= ~E1000_CTRL_EXT_FORCE_SMBUS;
Expand Down
182 changes: 113 additions & 69 deletions drivers/net/ethernet/intel/e1000e/phy.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ s32 e1000e_phy_reset_dsp(struct e1000_hw *hw)
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
* @hw: pointer to the HW structure
Expand All @@ -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)
{
u32 i, mdic = 0, retry_counter, retry_max;
struct e1000_phy_info *phy = &hw->phy;
u32 i, mdic = 0;
bool success;

if (offset > MAX_PHY_REG_ADDRESS) {
e_dbg("PHY Address %d is out of range\n", offset);
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
* Control register. The MAC will take care of interfacing with the
* PHY to retrieve the desired data.
*/
mdic = ((offset << E1000_MDIC_REG_SHIFT) |
(phy->addr << E1000_MDIC_PHY_SHIFT) |
(E1000_MDIC_OP_READ));
for (retry_counter = 0; retry_counter <= retry_max; retry_counter++) {
success = true;

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
* 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 Read PHY Reg Address %d did not complete\n", offset);
return -E1000_ERR_PHY;
}
if (mdic & E1000_MDIC_ERROR) {
e_dbg("MDI Read PHY Reg Address %d Error\n", offset);
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));
return -E1000_ERR_PHY;
ew32(MDIC, mdic);

/* Poll the ready bit to see if the MDI read completed
* Increasing the time out as testing showed failures with
* the lower time out
*/
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 Read PHY Reg Address %d did not complete\n",
offset);
success = false;
}
if (mdic & E1000_MDIC_ERROR) {
e_dbg("MDI Read PHY Reg Address %d Error\n", offset);
success = false;
}
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
* reading duplicate data in the next MDIC transaction.
*/
if (hw->mac.type == e1000_pch2lan)
udelay(100);
return 0;
return -E1000_ERR_PHY;
}

/**
Expand All @@ -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)
{
u32 i, mdic = 0, retry_counter, retry_max;
struct e1000_phy_info *phy = &hw->phy;
u32 i, mdic = 0;
bool success;

if (offset > MAX_PHY_REG_ADDRESS) {
e_dbg("PHY Address %d is out of range\n", offset);
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
* Control register. The MAC will take care of interfacing with the
* PHY to retrieve the desired data.
*/
mdic = (((u32)data) |
(offset << E1000_MDIC_REG_SHIFT) |
(phy->addr << E1000_MDIC_PHY_SHIFT) |
(E1000_MDIC_OP_WRITE));
for (retry_counter = 0; retry_counter <= retry_max; retry_counter++) {
success = true;

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
* 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;
}
ew32(MDIC, mdic);

/* Allow some time after each MDIC transaction to avoid
* reading duplicate data in the next MDIC transaction.
*/
if (hw->mac.type == e1000_pch2lan)
udelay(100);
/* Poll the ready bit to see if the MDI read completed
* Increasing the time out as testing showed failures with
* the lower time out
*/
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;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions drivers/net/ethernet/intel/e1000e/phy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
void e1000_power_up_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_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);
Expand Down

0 comments on commit 6dbdd4d

Please sign in to comment.