diff options
Diffstat (limited to 'drivers/net/ethernet/intel/e1000/e1000_oem_phy.c')
-rw-r--r-- | drivers/net/ethernet/intel/e1000/e1000_oem_phy.c | 2449 |
1 files changed, 2449 insertions, 0 deletions
diff --git a/drivers/net/ethernet/intel/e1000/e1000_oem_phy.c b/drivers/net/ethernet/intel/e1000/e1000_oem_phy.c new file mode 100644 index 00000000000..92c39c90473 --- /dev/null +++ b/drivers/net/ethernet/intel/e1000/e1000_oem_phy.c @@ -0,0 +1,2449 @@ +/***************************************************************************** + +GPL LICENSE SUMMARY + + Copyright(c) 2007,2008,2009 Intel Corporation. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + The full GNU General Public License is included in this distribution + in the file called LICENSE.GPL. + + Contact Information: + Intel Corporation + + version: Embedded.Release.Patch.L.1.0.7-5 + + Contact Information: + + Intel Corporation, 5000 W Chandler Blvd, Chandler, AZ 85226 + +*****************************************************************************/ +/************************************************************************** + * @ingroup OEM_PHY_GENERAL + * + * @file oem_phy.c + * + * @description + * This module contains oem functions. + * + **************************************************************************/ + +#include "e1000_oem_phy.h" +#include "e1000.h" +#include "e1000_hw.h" +#include "gcu_if.h" +/* + * List of functions leveraged from the base e1000 driver. + * + * Ideally, it would have been nice to keep e1000_oem_phy.c + * minimally dependent on the e1000. Any function taking + * a struct e1000_hw as a parameter can be implemented in + * this file. It was chosen to reuse as much code as possible + * to save time (isn't that always the case ;) + */ +extern int e1000_up(struct e1000_adapter *adapter); +extern void e1000_down(struct e1000_adapter *adapter); +extern void e1000_reset(struct e1000_adapter *adapter); +extern int32_t e1000_copper_link_autoneg(struct e1000_hw *hw); +extern int32_t e1000_phy_force_speed_duplex(struct e1000_hw *hw); +extern int32_t e1000_copper_link_postconfig(struct e1000_hw *hw); +extern int32_t e1000_oem_set_trans_gasket(struct e1000_hw *hw); + +/* forward declarations for static support functions */ +static int32_t e1000_oem_link_m88_setup(struct e1000_hw *hw); +static int32_t e1000_oem_link_v8211_setup(struct e1000_hw *hw); +static int32_t e1000_oem_link_v8601_setup(struct e1000_hw *hw); +static int32_t e1000_oem_set_phy_mode(struct e1000_hw *hw); +static int32_t e1000_oem_detect_phy(struct e1000_hw *hw); + +#define NON_PHY_PORT 0xFFFFFFFF +#define SILVERTIP_BC5860 + +/** + * e1000_oem_setup_link + * @hw: e1000_hw struct containing device specific information + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * Performs OEM Transceiver specific link setup as part of the + * global e1000_setup_link() function. + **/ +int32_t e1000_oem_setup_link(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + /* + * see e1000_setup_copper_link() as the primary example. Look at both + * the M88 and IGP functions that are called for ideas, possibly for + * power management. + */ + + int32_t ret_val; + uint32_t ctrl; + uint16_t i; + uint16_t phy_data; + + e_dbg("%s", __func__); + + if (!hw) + return -1; + + /* AFU: add test to exit out if improper phy type + */ + /* relevent parts of e1000_copper_link_preconfig */ + ctrl = E1000_READ_REG(hw, CTRL); + ctrl |= E1000_CTRL_SLU; + ctrl &= ~(E1000_CTRL_FRCSPD | E1000_CTRL_FRCDPX); + E1000_WRITE_REG(hw, CTRL, ctrl); + + /* this is required for *hw init */ + ret_val = e1000_oem_detect_phy(hw); + if (ret_val) + return ret_val; + ret_val = e1000_oem_set_phy_mode(hw); + if (ret_val) + return ret_val; + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + ret_val = e1000_oem_link_m88_setup(hw); + if (ret_val) + return ret_val; + break; + case VSC8211_E_PHY_ID: + ret_val = e1000_oem_link_v8211_setup(hw); + if (ret_val) + return ret_val; + break; + case VSC8601_E_PHY_ID: + ret_val = e1000_oem_link_v8601_setup(hw); + if (ret_val) + return ret_val; + break; + + case NON_PHY_PORT: + hw->icp_xxxx_is_link_up = true; + /* Reture for skipping the latter blocks about autoneg and + link status checking */ + return E1000_SUCCESS; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + if (hw->autoneg) { + ret_val = e1000_copper_link_autoneg(hw); + if (ret_val) + return ret_val; + } else { + e_dbg("Forcing speed and duplex\n"); + ret_val = e1000_phy_force_speed_duplex(hw); + } + + /* + * Check link status. Wait up to 100 microseconds for link to become + * valid. + */ + for (i = 0; i < 0xa; i++) { + ret_val = e1000_oem_read_phy_reg_ex(hw, PHY_STATUS, &phy_data); + if (ret_val) { + e_dbg("Unable to read register PHY_STATUS\n"); + return ret_val; + } + + ret_val = e1000_oem_read_phy_reg_ex(hw, PHY_STATUS, &phy_data); + if (ret_val) { + e_dbg("Unable to read register PHY_STATUS\n"); + return ret_val; + } + + hw->icp_xxxx_is_link_up = (phy_data & MII_SR_LINK_STATUS) != 0; + + if (phy_data & MII_SR_LINK_STATUS) { + /* Config the MAC and PHY after link is up */ + ret_val = e1000_copper_link_postconfig(hw); + if (ret_val) + return ret_val; + e_dbg("Valid link established!!!\n"); + return E1000_SUCCESS; + } + udelay(0xa); + } + + e_dbg("Unable to establish link!!!\n"); + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + e_dbg("Invalid value for hw->media_type"); + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_link_m88_setup + * @hw: e1000_hw struct containing device specific information + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * lifted from e1000_copper_link_mgp_setup, pretty much + * copied verbatim except replace e1000_phy_reset with e1000_phy_hw_reset + **/ +static int32_t e1000_oem_link_m88_setup(struct e1000_hw *hw) +{ + int32_t ret_val; + uint16_t phy_data = 0; + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + ret_val = e1000_oem_read_phy_reg_ex(hw, M88E1000_PHY_SPEC_CTRL, + &phy_data); + phy_data |= 0x00000008; + ret_val = + e1000_oem_write_phy_reg_ex(hw, M88E1000_PHY_SPEC_CTRL, phy_data); + + /* phy_reset_disable is set in e1000_oem_set_phy_mode */ + if (hw->phy_reset_disable) + return E1000_SUCCESS; + + /* Enable CRS on TX. This must be set for half-duplex operation. */ + ret_val = + e1000_oem_read_phy_reg_ex(hw, M88E1000_PHY_SPEC_CTRL, &phy_data); + if (ret_val) { + e_dbg("Unable to read M88E1000_PHY_SPEC_CTRL register\n"); + return ret_val; + } + + phy_data &= ~M88E1000_PSCR_ASSERT_CRS_ON_TX; + + /* + * Options: + * MDI/MDI-X = 0 (default) + * 0 - Auto for all speeds + * 1 - MDI mode + * 2 - MDI-X mode + * 3 - Auto for 1000Base-T only (MDI-X for 10/100Base-T modes) + */ + phy_data &= ~M88E1000_PSCR_AUTO_X_MODE; + + switch (hw->mdix) { + case 0x1: + phy_data |= M88E1000_PSCR_MDI_MANUAL_MODE; + break; + case 0x2: + phy_data |= M88E1000_PSCR_MDIX_MANUAL_MODE; + break; + case 0x3: + phy_data |= M88E1000_PSCR_AUTO_X_1000T; + break; + case 0: + default: + phy_data |= M88E1000_PSCR_AUTO_X_MODE; + break; + } + + /* + * Options: + * disable_polarity_correction = 0 (default) + * Automatic Correction for Reversed Cable Polarity + * 0 - Disabled + * 1 - Enabled + */ + phy_data &= ~M88E1000_PSCR_POLARITY_REVERSAL; + + if (hw->disable_polarity_correction == 1) + phy_data |= M88E1000_PSCR_POLARITY_REVERSAL; + + ret_val = + e1000_oem_write_phy_reg_ex(hw, M88E1000_PHY_SPEC_CTRL, phy_data); + if (ret_val) { + e_dbg("Unable to write M88E1000_PHY_SPEC_CTRL register\n"); + return ret_val; + } + + /* + * Force TX_CLK in the Extended PHY Specific Control Register + * to 25MHz clock. + */ + ret_val = e1000_oem_read_phy_reg_ex(hw, M88E1000_EXT_PHY_SPEC_CTRL, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read M88E1000_EXT_PHY_SPEC_CTRL register\n"); + return ret_val; + } + + /* + * For Truxton, it is necessary to add RGMII tx and rx + * timing delay though the EXT_PHY_SPEC_CTRL register + */ + phy_data |= M88E1000_EPSCR_TX_TIME_CTRL; + phy_data |= M88E1000_EPSCR_RX_TIME_CTRL; + + if (hw->phy_revision < M88E1011_I_REV_4) { + + phy_data |= M88E1000_EPSCR_TX_CLK_25; + /* Configure Master and Slave downshift values */ + phy_data &= ~(M88E1000_EPSCR_MASTER_DOWNSHIFT_MASK | + M88E1000_EPSCR_SLAVE_DOWNSHIFT_MASK); + phy_data |= (M88E1000_EPSCR_MASTER_DOWNSHIFT_1X | + M88E1000_EPSCR_SLAVE_DOWNSHIFT_1X); + } + ret_val = e1000_oem_write_phy_reg_ex(hw, M88E1000_EXT_PHY_SPEC_CTRL, + phy_data); + if (ret_val) { + e_dbg + ("Unable to read M88E1000_EXT_PHY_SPEC_CTRL register\n"); + return ret_val; + } + + /* SW Reset the PHY so all changes take effect */ + ret_val = e1000_phy_hw_reset(hw); + if (ret_val) { + e_dbg("Error Resetting the PHY\n"); + return ret_val; + } + + return E1000_SUCCESS; +} + +/** + * e1000_oem_link_v8211_setup + * @hw: e1000_hw struct containing device specific information + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * lifted from e1000_copper_link_mgp_setup, pretty much + * copied verbatim except replace e1000_phy_reset with e1000_phy_hw_reset + **/ +static int32_t e1000_oem_link_v8211_setup(struct e1000_hw *hw) +{ + int32_t ret_val; + uint16_t phy_data; + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + /* phy_reset_disable is set in e1000_oem_set_phy_mode */ + if (hw->phy_reset_disable) + return E1000_SUCCESS; + + /* + * Options: + * disable_polarity_correction = 0 (default) + * Automatic Correction for Reversed Cable Polarity + * 0 - Disabled + * 1 - Enabled + */ + ret_val = e1000_oem_read_phy_reg_ex(hw, VSC8211_BYPASS_CTRL, &phy_data); + if (ret_val) { + e_dbg("Unable to read VSC8211_BYPASS_CTRL register\n"); + return ret_val; + } + if (hw->disable_polarity_correction) + phy_data |= VSC8211_BYPASS_POLAR_INVERS_DISABLE; + else + phy_data &= ~VSC8211_BYPASS_POLAR_INVERS_DISABLE; + + e1000_oem_write_phy_reg_ex(hw, VSC8211_BYPASS_CTRL, phy_data); + + if (ret_val) { + e_dbg("Unable to write VSC8211_BYPASS_CTRL register\n"); + return ret_val; + } + + /* Options: + * MDI/MDI-X = 0 (default) + * 0 - Auto for all speeds + * 1 - MDI mode + * 2 - MDI-X mode + * 3 - Auto for 1000Base-T only (MDI-X for 10/100Base-T modes) + */ + switch (hw->mdix) { + default: + break; + } + + /* SW Reset the PHY so all changes take effect */ + ret_val = e1000_phy_hw_reset(hw); + if (ret_val) { + e_dbg("Error Resetting the PHY\n"); + return ret_val; + } + + return E1000_SUCCESS; +} + +/** + * e1000_oem_link_v8601_setup + * @hw: e1000_hw struct containing device specific information + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * lifted from e1000_copper_link_mgp_setup, pretty much + * copied verbatim except replace e1000_phy_reset with e1000_phy_hw_reset + **/ +static int32_t e1000_oem_link_v8601_setup(struct e1000_hw *hw) +{ + int32_t ret_val; + uint16_t phy_data; + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + /* phy_reset_disable is set in e1000_oem_set_phy_mode */ + if (hw->phy_reset_disable) + return E1000_SUCCESS; + + /* + * Options: + * disable_polarity_correction = 0 (default) + * Automatic Correction for Reversed Cable Polarity + * 0 - Disabled + * 1 - Enabled + */ + ret_val = e1000_oem_read_phy_reg_ex(hw, VSC8601_BYPASS_CTRL, &phy_data); + if (ret_val) { + e_dbg("Unable to read VSC8601_BYPASS_CTRL register\n"); + return ret_val; + } + if (hw->disable_polarity_correction) + phy_data |= VSC8601_BYPASS_POLAR_INVERS_DISABLE; + else + phy_data &= ~VSC8601_BYPASS_POLAR_INVERS_DISABLE; + + e1000_oem_write_phy_reg_ex(hw, VSC8601_BYPASS_CTRL, phy_data); + + if (ret_val) { + e_dbg("Unable to write VSC8601_BYPASS_CTRL register\n"); + return ret_val; + } + + /* Options: + * MDI/MDI-X = 0 (default) + * 0 - Auto for all speeds + * 1 - MDI mode + * 2 - MDI-X mode + * 3 - Auto for 1000Base-T only (MDI-X for 10/100Base-T modes) + */ + switch (hw->mdix) { + default: + break; + } + + /* SW Reset the PHY so all changes take effect */ + ret_val = e1000_phy_hw_reset(hw); + if (ret_val) { + e_dbg("Error Resetting the PHY\n"); + return ret_val; + } + // SDG - this is done to setup the LED mode correctly but should really + // be fixed in the hardware + phy_data = 0x0001; + e1000_oem_write_phy_reg_ex(hw, 31, phy_data); // switch to extended page registers + // phy_data = 0x0C17; // just link + phy_data = 0x0410; // blink mode + e1000_oem_write_phy_reg_ex(hw, 17, phy_data); // enhanced led mode, + // no activity (done by turning off combination feature) + phy_data = 0x0021; + e1000_oem_write_phy_reg_ex(hw, 16, phy_data); // link100 and link1000 + phy_data = 0x0000; + e1000_oem_write_phy_reg_ex(hw, 31, phy_data); // switch back to standard page registers + + return E1000_SUCCESS; +} + +/** + * e1000_oem_force_mdi + * @hw: e1000_hw struct containing device specific information + * @resetPhy: returns true if after calling this function the + * PHY requires a reset + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * This is called from e1000_phy_force_speed_duplex, which is + * called from e1000_oem_setup_link. + **/ +int32_t e1000_oem_force_mdi(struct e1000_hw *hw, int *resetPhy) +{ +#ifdef EXTERNAL_MDIO + + uint16_t phy_data; + int32_t ret_val; + + if (!hw || !resetPhy) + return -1; + + e_dbg("%s", __func__); + + /* + * a boolean to indicate if the phy needs to be reset + * + * Make note that the M88 phy is what'll be used on Truxton + * see e1000_phy_force_speed_duplex, which does the following for M88 + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + ret_val = e1000_oem_read_phy_reg_ex(hw, + M88E1000_PHY_SPEC_CTRL, + &phy_data); + if (ret_val) { + e_dbg("Unable to read M88E1000_PHY_SPEC_CTRL " + "register\n"); + return ret_val; + } + + /* + * Clear Auto-Crossover to force MDI manually. M88E1000 requires + * MDI forced whenever speed are duplex are forced. + */ + + phy_data &= ~M88E1000_PSCR_AUTO_X_MODE; + ret_val = e1000_oem_write_phy_reg_ex(hw, M88E1000_PHY_SPEC_CTRL, + phy_data); + if (ret_val) { + e_dbg("Unable to write M88E1000_PHY_SPEC_CTRL " + "register\n"); + return ret_val; + } + *resetPhy = true; + break; + case VSC8211_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, VSC8211_BYPASS_CTRL, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read VSC8211_BYPASS_CTRL register\n"); + return ret_val; + } + /* disable automatic MDI and MDI-X */ + phy_data |= VSC8211_BYPASS_AUTO_MDI_DISABLE; + e1000_oem_write_phy_reg_ex(hw, VSC8211_BYPASS_CTRL, phy_data); + if (ret_val) { + e_dbg + ("Unable to write VSC8211_BYPASS_CTRL register\n"); + return ret_val; + } + *resetPhy = true; + break; + case VSC8601_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, VSC8601_BYPASS_CTRL, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read VSC8601_BYPASS_CTRL register\n"); + return ret_val; + } + /* disable automatic MDI and MDI-X */ + phy_data |= VSC8601_BYPASS_AUTO_MDI_DISABLE; + e1000_oem_write_phy_reg_ex(hw, VSC8601_BYPASS_CTRL, phy_data); + if (ret_val) { + e_dbg + ("Unable to write VSC8601_BYPASS_CTRL register\n"); + return ret_val; + } + *resetPhy = true; + break; + + case NON_PHY_PORT: + *resetPhy = false; + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + if (!hw || !resetPhy) + return -1; + + *resetPhy = false; + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_reset_dsp + * @hw: e1000_hw struct containing device specific information + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * This is called from e1000_phy_force_speed_duplex, which is + * called from e1000_oem_setup_link. + **/ +int32_t e1000_oem_phy_reset_dsp(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + /* + * Make note that the M88 phy is what'll be used on Truxton. + * + * See e1000_phy_force_speed_duplex, which calls e1000_phy_reset_dsp + * for the M88 PHY. The code as written references registers 29 and 30, + * which are reserved for the M88 used on Truxton, so this will be a + * no-op. + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + case NON_PHY_PORT: + e_dbg("No DSP to reset on OEM PHY\n"); + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_cleanup_after_phy_reset + * @hw: e1000_hw struct containing device specific information + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * This is called from e1000_phy_force_speed_duplex, which is + * called from e1000_oem_setup_link. + **/ +int32_t e1000_oem_cleanup_after_phy_reset(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + uint16_t phy_data; + int32_t ret_val; + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + /* + * Make note that the M88 phy is what'll be used on Truxton. + * see e1000_phy_force_speed_duplex, which does the following for M88 + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + /* + * Because we reset the PHY above, we need to re-force + * TX_CLK in the Extended PHY Specific Control Register to + * 25MHz clock. This value defaults back to a 2.5MHz clock + * when the PHY is reset. + */ + + ret_val = e1000_oem_read_phy_reg_ex(hw, + M88E1000_EXT_PHY_SPEC_CTRL, + &phy_data); + if (ret_val) { + e_dbg("Unable to read M88E1000_EXT_SPEC_CTRL " + "register\n"); + return ret_val; + } + + phy_data |= M88E1000_EPSCR_TX_CLK_25; + ret_val = e1000_oem_write_phy_reg_ex(hw, + M88E1000_EXT_PHY_SPEC_CTRL, + phy_data); + if (ret_val) { + e_dbg("Unable to write M88E1000_EXT_PHY_SPEC_CTRL " + "register\n"); + return ret_val; + } + + /* + * In addition, because of the s/w reset above, we need to + * enable CRX on TX. This must be set for both full and half + * duplex operation. + */ + + ret_val = e1000_oem_read_phy_reg_ex(hw, + M88E1000_PHY_SPEC_CTRL, + &phy_data); + if (ret_val) { + e_dbg("Unable to read M88E1000_PHY_SPEC_CTRL " + "register\n"); + return ret_val; + } + + phy_data &= ~M88E1000_PSCR_ASSERT_CRS_ON_TX; + ret_val = e1000_oem_write_phy_reg_ex(hw, M88E1000_PHY_SPEC_CTRL, + phy_data); + if (ret_val) { + e_dbg("Unable to write M88E1000_PHY_SPEC_CTRL " + "register\n"); + return ret_val; + } + break; + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + case NON_PHY_PORT: + /* do nothing */ + break; + + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_set_phy_mode + * @hw: e1000_hw struct containing device specific information + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * This is called from e1000_oem_setup_link which is + * called from e1000_setup_link. + **/ +static int32_t e1000_oem_set_phy_mode(struct e1000_hw *hw) +{ + /* + * it is unclear if it is necessary to set the phy mode. Right now only + * one MAC 82545 Rev 3 does it, but the other MACs like tola do not. + * Leave the functionality off for now until it is determined that + * Tolapai needs it as well. + */ +#ifdef skip_set_mode +#undef skip_set_mode +#endif + +#ifdef skip_set_mode + int32_t ret_val; + uint16_t eeprom_data; +#endif + if (!hw) + return -1; + + e_dbg("%s", __func__); + + /* + * e1000_set_phy_mode specifically works for 82545 Rev 3 only, + * since it is a 'loner' compared to the 82545, 82546, and + * 82546 Rev 3, assume for now it is anomaly and don't repeat + * for Truxton/Haxton. + * Note that this is the approach taken in both the Windows and + * FreeBSD drivers + */ + + switch (hw->phy_id) { + case VSC8211_E_PHY_ID: + { + int32_t ret_val; + int16_t phy_data; + /* Set CMODE to the RGMII-CAT5 combination */ + phy_data = + VSC8211_PHY_CTRL1_INTF_MODE1_RGMII | + VSC8211_PHY_CTRL1_TXC_SKEW_2NS | + VSC8211_PHY_CTRL1_RXC_SKEW_2NS | + VSC8211_PHY_CTRL1_RX_IDLE_CLK_ENABLE | + VSC8211_PHY_CTRL1_INTF_MODE2_CAT5; + ret_val = + e1000_oem_write_phy_reg_ex(hw, VSC8211_PHY_CTRL_1, + phy_data); + if (ret_val) { + e_dbg("Unable to write VSC8211_PHY_CTRL_1 " + "register\n"); + return ret_val; + } + break; + } + } + +#ifndef skip_set_mode + e_dbg("No need to call oem_set_phy_mode on Truxton\n"); +#else + /* + * Make note that the M88 phy is what'll be used on Truxton. + * + * use e1000_set_phy_mode as example + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + ret_val = e1000_read_eeprom(hw, + EEPROM_PHY_CLASS_WORD, + 1, &eeprom_data); + if (ret_val) + return ret_val; + + if ((eeprom_data != EEPROM_RESERVED_WORD) && + (eeprom_data & EEPROM_PHY_CLASS_A)) { + ret_val = e1000_oem_write_phy_reg_ex(hw, + M88E1000_PHY_PAGE_SELECT, 0x000B); + if (ret_val) { + e_dbg("Unable to write to " + "M88E1000_PHY_PAGE_SELECT register " + "on PHY\n"); + return ret_val; + } + + ret_val = e1000_oem_write_phy_reg_ex(hw, + M88E1000_PHY_GEN_CONTROL, 0x8104); + if (ret_val) { + e_dbg("Unable to write to " + "M88E1000_PHY_GEN_CONTROL register" + "on PHY\n"); + return ret_val; + } + + hw->phy_reset_disable = false; + } + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } +#endif + + return E1000_SUCCESS; + +} + +/** + * e1000_oem_detect_phy + * @hw: e1000_hw struct containing device specific information + * + * Fills hw->phy_type, hw->phy_id and hw->phy_revision fields as well + * as verifies that the PHY identified is one that is comprehended + * by the driver. + * + * This borrows heavily from e1000_detect_gig_phy + **/ +static int32_t e1000_oem_detect_phy(struct e1000_hw *hw) +{ + int32_t ret_val; + uint16_t phy_id_high, phy_id_low; + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + hw->phy_type = e1000_phy_oem; + + { + struct e1000_adapter *adapter; + uint32_t dev_num; + adapter = (struct e1000_adapter *)hw->back; + dev_num = PCI_SLOT(adapter->pdev->devfn); + switch (dev_num) { +#ifndef CONFIG_E1000_EP80579_PHY0 + case ICP_XXXX_MAC_0: + hw->phy_id = NON_PHY_PORT; + return E1000_SUCCESS; +#endif +#ifndef CONFIG_E1000_EP80579_PHY1 + case ICP_XXXX_MAC_1: + hw->phy_id = NON_PHY_PORT; + return E1000_SUCCESS; +#endif +#ifndef CONFIG_E1000_EP80579_PHY2 + case ICP_XXXX_MAC_2: + hw->phy_id = NON_PHY_PORT; + return E1000_SUCCESS; +#endif + } + } + + ret_val = e1000_oem_read_phy_reg_ex(hw, PHY_ID1, &phy_id_high); + if (ret_val) { + e_dbg("Unable to read PHY register PHY_ID1\n"); + return ret_val; + } + + udelay(0x14); + ret_val = e1000_oem_read_phy_reg_ex(hw, PHY_ID2, &phy_id_low); + if (ret_val) { + e_dbg("Unable to read PHY register PHY_ID2\n"); + return ret_val; + } + hw->phy_id = (uint32_t) ((phy_id_high << 0x10) + + (phy_id_low & PHY_REVISION_MASK)); + hw->phy_revision = (uint32_t) phy_id_low & ~PHY_REVISION_MASK; + + return E1000_SUCCESS; +} + +/** + * e1000_oem_get_tipg + * @hw: e1000_hw struct containing device specific information + * + * Returns the value of the Inter Packet Gap (IPG) Transmit Time (IPGT) in the + * Transmit IPG register appropriate for the given PHY. This field is only 10 + * bits wide. + * + * In the original e1000 code, only the IPGT field varied between media types. + * If the OEM phy requires setting IPG Receive Time 1 & 2 Registers, it would + * be required to modify the e1000_config_tx() function to accomdate the change + * + **/ +uint32_t e1000_oem_get_tipg(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + uint32_t phy_num; + + if (!hw) + return DEFAULT_ICP_XXXX_TIPG_IPGT; + + e_dbg("%s", __func__); + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + case NON_PHY_PORT: + phy_num = DEFAULT_ICP_XXXX_TIPG_IPGT; + break; + default: + e_dbg("Invalid PHY ID\n"); + return DEFAULT_ICP_XXXX_TIPG_IPGT; + } + + return phy_num; + +#else /* ifdef EXTERNAL_MDIO */ + + /* return the default value required by ICP_xxxx style MACS */ + e_dbg("Invalid value for transceiver type, return default" + " TIPG.IPGT value\n"); + return DEFAULT_ICP_XXXX_TIPG_IPGT; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_is_copper + * @hw: e1000_hw struct containing device specific information + * + * Test for media type within the e1000 driver is common, so this is a simple + * test for copper PHYs. The ICP_XXXX family of controllers initially only + * supported copper interconnects (no TBI (ten bit interface) for Fiber + * existed). If future revs support either Fiber or an internal SERDES, it + * may become necessary to evaluate where this function is used to go beyond + * determining whether or not media type is just copper. + * + **/ +int e1000_oem_phy_is_copper(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + int isCopper = true; + + if (!hw) + return isCopper; + + e_dbg("%s", __func__); + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + isCopper = true; + break; + case NON_PHY_PORT: + isCopper = false; + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return isCopper; + +#else /* ifdef EXTERNAL_MDIO */ + + /* + * caught between returning true or false. True allows it to + * be entered into && statements w/o ill effect, but false + * would make more sense + */ + e_dbg("Invalid value for transceiver type, return false\n"); + return false; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_get_phy_dev_number + * @hw: e1000_hw struct containing device specific information + * + * For ICP_XXXX family of devices, there are 3 MACs, each of which may + * have a different PHY (and indeed a different media interface). This + * function is used to indicate which of the MAC/PHY pairs we are interested + * in. + * + **/ +uint32_t e1000_oem_get_phy_dev_number(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + /* + * for ICP_XXXX family of devices, the three network interfaces are + * differentiated by their PCI device number, where the three share + * the same PCI bus + */ + struct e1000_adapter *adapter; + uint32_t device_number; + + if (!hw) + return 0; + + e_dbg("%s", __func__); + + adapter = (struct e1000_adapter *)hw->back; + device_number = PCI_SLOT(adapter->pdev->devfn); + + switch (device_number) { + case ICP_XXXX_MAC_0: + hw->phy_addr = 0x00; + break; + case ICP_XXXX_MAC_1: + hw->phy_addr = 0x01; + break; + case ICP_XXXX_MAC_2: + hw->phy_addr = 0x02; + break; + default: + hw->phy_addr = 0x00; + } + return hw->phy_addr; + +#else /* ifdef EXTERNAL_MDIO */ + e_dbg("Invalid value for transceiver type, return 0\n"); + return 0; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_mii_ioctl + * @adapter: e1000_hw struct containing device specific information + * @flags: The saved adapter->stats_lock flags from the initiating spinlock + * @ifr: interface request structure for socket ioctls + * @cmd: the original IOCTL command that instigated the call chain. + * + * This function abstracts out the code necessary to service the + * SIOCSMIIREG case within the e1000_mii_ioctl() for oem PHYs. + * e1000_mii_ioctl() was implemented for copper phy's only and this + * function will only be called if e1000_oem_phy_is_copper() returns true for + * a given MAC. Note that e1000_mii_ioctl() has a compile flag + * and exists only if SIOCGMIIPHY is defined. + * + * NOTE: a spinlock is in effect for the duration of this call. It is + * imperative that a negative value be returned on any error, so + * the spinlock can be released properly. + * + **/ +int +e1000_oem_mii_ioctl(struct e1000_adapter *adapter, unsigned long flags, + struct ifreq *ifr, int cmd) +{ +#ifdef EXTERNAL_MDIO + + struct mii_ioctl_data *data = if_mii(ifr); + uint16_t mii_reg = data->val_in; + uint32_t spd; + uint8_t dplx; + struct e1000_hw *hw; + int retval; + + if (!adapter || !ifr) + return -1; + + hw = &adapter->hw; + + e_dbg("%s", __func__); + + switch (data->reg_num) { + case PHY_CTRL: + if (mii_reg & MII_CR_POWER_DOWN) + break; + + if (mii_reg & MII_CR_AUTO_NEG_EN) { + adapter->hw.autoneg = 1; + adapter->hw.autoneg_advertised = + ICP_XXXX_AUTONEG_ADV_DEFAULT; + } else { + if (mii_reg & 0x40) + spd = SPEED_1000; + else if (mii_reg & 0x2000) + spd = SPEED_100; + else + spd = SPEED_10; + + dplx = (mii_reg & 0x100) ? FULL_DUPLEX : HALF_DUPLEX; + retval = e1000_set_spd_dplx(adapter, spd, dplx); + if (retval) + return retval; + + } + if (netif_running(adapter->netdev)) { + e1000_down(adapter); + e1000_up(adapter); + } else { + e1000_reset(adapter); + } + break; + case M88E1000_PHY_SPEC_CTRL: + case M88E1000_EXT_PHY_SPEC_CTRL: + retval = e1000_phy_reset(&adapter->hw); + if (retval) { + e_dbg("Error resetting the PHY\n"); + return -EIO; + } + break; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -EOPNOTSUPP; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_get_phy_regs + * @adapter e1000_adapter struct containing device specific information + * @data unsigned integer array of size data_len + * @data_len number of elements in data + * + * This is called by e1000_get_regs() in response to an ethtool request + * to return the data of the controller. Most of the data returned is from + * the MAC, but some data comes from the PHY, thus from this f(). + * + * Note: The call to e1000_get_regs() assumed an array of 24 elements + * where the last 11 are passed to this function. If the array + * that is passed to the calling function has its size or element + * defintions changed, this function becomes broken. + * + **/ +void e1000_oem_get_phy_regs(struct e1000_adapter *adapter, uint32_t *data, + uint32_t data_len) +{ + struct e1000_hw *hw; +#define EXPECTED_ARRAY_LEN 11 + uint32_t corrected_len; + + if (!adapter || !data) + return; + + hw = &adapter->hw; + + e_dbg("%s", __func__); + + /* This f(n) expects to have EXPECTED_ARRAY_LEN elements to initialize. + * Use the corrected_length variable to make sure we don't exceed that + * length + */ + corrected_len = data_len > EXPECTED_ARRAY_LEN + ? EXPECTED_ARRAY_LEN : data_len; + memset(data, 0, corrected_len * sizeof(uint32_t)); + +#ifdef EXTERNAL_MDIO + + /* + * Fill data[] with... + * + * [0] = cable length + * [1] = cable length + * [2] = cable length + * [3] = cable length + * [4] = extended 10bt distance + * [5] = cable polarity + * [6] = cable polarity + * [7] = polarity correction enabled + * [8] = undefined + * [9] = phy receive errors + * [10] = mdix mode + */ + switch (adapter->hw.phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + if (corrected_len > 0) + e1000_oem_read_phy_reg_ex(&adapter->hw, + M88E1000_PHY_SPEC_STATUS, + (uint16_t *) &data[0]); + + if (corrected_len > 0x1) + data[0x1] = 0x0; + /* Dummy (to align w/ IGP phy reg dump) */ + + if (corrected_len > 0x2) + data[0x2] = 0x0; + /* Dummy (to align w/ IGP phy reg dump) */ + + if (corrected_len > 0x3) + data[0x3] = 0x0; + /* Dummy (to align w/ IGP phy reg dump) */ + + if (corrected_len > 0x4) + e1000_oem_read_phy_reg_ex(&adapter->hw, + M88E1000_PHY_SPEC_CTRL, + (uint16_t *) &data[0x4]); + + if (corrected_len > 0x5) + data[0x5] = data[0x0]; + + if (corrected_len > 0x6) + data[0x6] = 0x0; + /* Dummy (to align w/ IGP phy reg dump) */ + + if (corrected_len > 0x7) + data[0x7] = data[0x4]; + + /* phy receive errors */ + if (corrected_len > 0x9) + data[0x9] = adapter->phy_stats.receive_errors; + + if (corrected_len > 0xa) + data[0xa] = data[0x0]; + + break; + default: + e_dbg("Invalid PHY ID\n"); + return; + } +#endif /* ifdef EXTERNAL_MDIO */ + +#undef EXPECTED_ARRAY_LEN + return; +} + +/** + * e1000_oem_phy_loopback + * @adapter e1000_adapter struct containing device specific information + * + * This is called from e1000_set_phy_loopback in response from call from + * ethtool to place the PHY into loopback mode. + **/ +int e1000_oem_phy_loopback(struct e1000_adapter *adapter) +{ +#ifdef EXTERNAL_MDIO + + int ret_val; + uint32_t ctrl_reg = 0; + struct e1000_hw *hw; + + if (!adapter) + return -1; + + hw = &adapter->hw; + e_dbg("%s", __func__); + + /* + * This borrows liberally from e1000_integrated_phy_loopback(). + * e1000_nonintegrated_phy_loopback() was also a point of reference + * since it was similar. The biggest difference between the two + * was that nonintegrated called e1000_phy_reset_clk_and_crs(), + * hopefully this won't matter as CRS required for half-duplex + * operation and this is set to full duplex. + * + * Make note that the M88 phy is what'll be used on Truxton + * Loopback configuration is the same for each of the supported PHYs. + */ + switch (adapter->hw.phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + + adapter->hw.autoneg = false; + + /* turn off Auto-MDI/MDIX */ + /*ret_val = e1000_oem_write_phy_reg_ex(&adapter->hw, + M88E1000_PHY_SPEC_CTRL, 0x0808); + if(ret_val) + { + e_dbg("Unable to write to register M88E1000_PHY_SPEC_CTRL\n"); + return ret_val; + } + */ + /* reset to update Auto-MDI/MDIX */ + /* ret_val = e1000_oem_write_phy_reg_ex(&adapter->hw, + PHY_CTRL, 0x9140); + if(ret_val) + { + e_dbg("Unable to write to register PHY__CTRL\n"); + return ret_val; + } + */ + /* autoneg off */ + /*ret_val = e1000_oem_write_phy_reg_ex(&adapter->hw, + PHY_CTRL, 0x8140); */ + ret_val = + e1000_oem_write_phy_reg_ex(&adapter->hw, PHY_CTRL, 0xa100); + if (ret_val) { + e_dbg("Unable to write to register PHY_CTRL\n"); + return ret_val; + } + + /* force 1000, set loopback */ + /*ret_val = + e1000_oem_write_phy_reg_ex(&adapter->hw, PHY_CTRL, 0x4140); */ + ret_val = + e1000_oem_write_phy_reg_ex(&adapter->hw, PHY_CTRL, 0x6100); + if (ret_val) { + e_dbg("Unable to write to register PHY_CTRL\n"); + return ret_val; + } + + ctrl_reg = E1000_READ_REG(&adapter->hw, CTRL); + ctrl_reg &= ~E1000_CTRL_SPD_SEL; /* Clear the speed sel bits */ + ctrl_reg |= (E1000_CTRL_FRCSPD /* Set the Force Speed Bit */ + | E1000_CTRL_FRCDPX /* Set the Force Duplex Bit */ + | E1000_CTRL_SPD_100 /* Force Speed to 1000 */ + | E1000_CTRL_FD); /* Force Duplex to FULL */ + /* | E1000_CTRL_ILOS); *//* Invert Loss of Signal */ + + E1000_WRITE_REG(&adapter->hw, CTRL, ctrl_reg); + + /* + * Write out to PHY registers 29 and 30 to disable the Receiver. + * This directly lifted from e1000_phy_disable_receiver(). + * + * The code is currently commented out as for the M88 used in + * Truxton, registers 29 and 30 are unutilized. Leave in, just + * in case we are on the receiving end of an 'undocumented' + * feature + */ + /* + * e1000_oem_write_phy_reg_ex(&adapter->hw, 29, 0x001F); + * e1000_oem_write_phy_reg_ex(&adapter->hw, 30, 0x8FFC); + * e1000_oem_write_phy_reg_ex(&adapter->hw, 29, 0x001A); + * e1000_oem_write_phy_reg_ex(&adapter->hw, 30, 0x8FF0); + */ + + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return 0; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ + +} + +/** + * e1000_oem_loopback_cleanup + * @adapter e1000_adapter struct containing device specific information + * + * This is called from e1000_loopback_cleanup in response from call from + * ethtool to place the PHY out of loopback mode. This handles the OEM + * specific part of loopback cleanup. + **/ +void e1000_oem_loopback_cleanup(struct e1000_adapter *adapter) +{ +#ifdef EXTERNAL_MDIO + + /* + * This borrows liberally from e1000_loopback_cleanup(). + * making note that the M88 phy is what'll be used on Truxton + * + * Loopback cleanup is the same for all supported PHYs. + */ + int32_t ret_val; + uint16_t phy_reg; + struct e1000_hw *hw; + + if (!adapter) + return; + hw = &adapter->hw; + e_dbg("%s", __func__); + + switch (adapter->hw.phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + default: + adapter->hw.autoneg = true; + + ret_val = e1000_oem_read_phy_reg_ex(&adapter->hw, PHY_CTRL, + &phy_reg); + if (ret_val) { + e_dbg("Unable to read to register PHY_CTRL\n"); + return; + } + + if (phy_reg & MII_CR_LOOPBACK) { + phy_reg &= ~MII_CR_LOOPBACK; + + ret_val = + e1000_oem_write_phy_reg_ex(&adapter->hw, PHY_CTRL, + phy_reg); + if (ret_val) { + e_dbg + ("Unable to write to register PHY_CTRL\n"); + return; + } + + e1000_phy_reset(&adapter->hw); + } + } + +#endif /* ifdef EXTERNAL_MDIO */ + return; + +} + +/** + * e1000_oem_phy_speed_downgraded + * @hw e1000_hw struct containing device specific information + * @isDowngraded returns with value > 0 if the link belonging to hw + * has been downshifted + * + * Called by e1000_check_downshift(), checks the PHY to see if it running + * at as speed slower than its maximum. + **/ +uint32_t +e1000_oem_phy_speed_downgraded(struct e1000_hw *hw, uint16_t *isDowngraded) +{ +#ifdef EXTERNAL_MDIO + + uint32_t ret_val; + uint16_t phy_data; + + if (!hw || !isDowngraded) + return 1; + + e_dbg("%s", __func__); + + /* + * borrow liberally from E1000_check_downshift e1000_phy_m88 case. + * Make note that the M88 phy is what'll be used on Truxton + */ + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, M88E1000_PHY_SPEC_STATUS, + &phy_data); + if (ret_val) { + e_dbg("Unable to read register " + "M88E1000_PHY_SPEC_STATUS\n"); + return ret_val; + } + + *isDowngraded = (phy_data & M88E1000_PSSR_DOWNSHIFT) + >> M88E1000_PSSR_DOWNSHIFT_SHIFT; + + break; + + case VSC8211_E_PHY_ID: + ret_val = e1000_oem_read_phy_reg_ex(hw, VSC8211_AUX_CTRL_STS, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read register VSC8211_AUX_CTRL_STS\n"); + return ret_val; + } + *isDowngraded = (phy_data & VSC8211_AUX_SPEED_MASK) != + VSC8211_AUX_SPEED_IS_1000; + break; + + case VSC8601_E_PHY_ID: + ret_val = e1000_oem_read_phy_reg_ex(hw, VSC8601_AUX_CTRL_STS, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read register VSC8601_AUX_CTRL_STS\n"); + return ret_val; + } + *isDowngraded = (phy_data & VSC8601_AUX_SPEED_MASK) != + VSC8601_AUX_SPEED_IS_1000; + break; + + default: + e_dbg("Invalid PHY ID\n"); + return 1; + } + + return 0; + +#else /* ifdef EXTERNAL_MDIO */ + + if (!hw || !isDowngraded) + return 1; + + *isDowngraded = 0; + return 0; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_check_polarity + * @hw e1000_hw struct containing device specific information + * @isDowngraded returns with value > 0 if the link belonging to hw + * has its polarity shifted. + * + * Called by e1000_check_downshift(), checks the PHY to see if it running + * at as speed slower than its maximum. + **/ +int32_t e1000_oem_check_polarity(struct e1000_hw *hw, uint16_t *polarity) +{ +#ifdef EXTERNAL_MDIO + + int32_t ret_val; + uint16_t phy_data; + + if (!hw || !polarity) + return -1; + + e_dbg("%s", __func__); + + /* + * borrow liberally from e1000_check_polarity. + * Make note that the M88 phy is what'll be used on Truxton + */ + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + /* return the Polarity bit in the Status register. */ + ret_val = e1000_oem_read_phy_reg_ex(hw, + M88E1000_PHY_SPEC_STATUS, + &phy_data); + if (ret_val) { + e_dbg("Unable to read register " + "M88E1000_PHY_SPEC_STATUS\n"); + return ret_val; + } + + *polarity = (phy_data & M88E1000_PSSR_REV_POLARITY) + >> M88E1000_PSSR_REV_POLARITY_SHIFT; + + break; + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + e_dbg("check polarity is not supported by VSC8XXX\n"); + return -E1000_ERR_PHY_TYPE; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + return 0; + +#else /* ifdef EXTERNAL_MDIO */ + + if (!hw || !polarity) + return -1; + + *polarity = 0; + return -1; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_is_full_duplex + * @hw e1000_hw struct containing device specific information + * @isFD a boolean returning true if phy is full duplex + * + * This is called as part of e1000_config_mac_to_phy() to align + * the MAC with the PHY. It turns out on ICP_XXXX, this is not + * done automagically. + **/ +int32_t e1000_oem_phy_is_full_duplex(struct e1000_hw *hw, int *isFD) +{ +#ifdef EXTERNAL_MDIO + + uint16_t phy_data; + int32_t ret_val; + + if (!hw || !isFD) + return -1; + + e_dbg("%s", __func__); + + /* + * Make note that the M88 phy is what'll be used on Truxton + * see e1000_config_mac_to_phy + */ + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, M88E1000_PHY_SPEC_STATUS, + &phy_data); + if (ret_val) { + e_dbg("Unable to read register " + "M88E1000_PHY_SPEC_STATUS\n"); + return ret_val; + } + *isFD = (phy_data & M88E1000_PSSR_DPLX) != 0; + + break; + case VSC8211_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, VSC8211_AUX_CTRL_STS, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read register VSC8211_AUX_CTRL_STS\n"); + return ret_val; + } + *isFD = + (phy_data & VSC8211_AUX_FDX_MASK) == + VSC8211_AUX_FDX_IS_FULL; + break; + case VSC8601_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, VSC8601_AUX_CTRL_STS, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read register VSC8601_AUX_CTRL_STS\n"); + return ret_val; + } + *isFD = + (phy_data & VSC8601_AUX_FDX_MASK) == + VSC8601_AUX_FDX_IS_FULL; + break; + + case NON_PHY_PORT: + *isFD = true; + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + if (!hw || !isFD) + return -1; + *isFD = false; + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_is_speed_1000 + * @hw e1000_hw struct containing device specific information + * @is1000 a boolean returning true if phy is running at 1000 + * + * This is called as part of e1000_config_mac_to_phy() to align + * the MAC with the PHY. It turns out on ICP_XXXX, this is not + * done automagically. + **/ +int32_t e1000_oem_phy_is_speed_1000(struct e1000_hw *hw, int *is1000) +{ +#ifdef EXTERNAL_MDIO + + uint16_t phy_data; + int32_t ret_val; + + if (!hw || !is1000) + return -1; + + e_dbg("%s", __func__); + + /* + * Make note that the M88 phy is what'll be used on Truxton. + * see e1000_config_mac_to_phy + */ + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, M88E1000_PHY_SPEC_STATUS, + &phy_data); + if (ret_val) { + e_dbg("Unable to read register " + "M88E1000_PHY_SPEC_STATUS\n"); + return ret_val; + } + *is1000 = + (phy_data & M88E1000_PSSR_SPEED) == M88E1000_PSSR_1000MBS; + break; + case VSC8211_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, VSC8211_AUX_CTRL_STS, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read register VSC8211_AUX_CTRL_STS\n"); + return ret_val; + } + *is1000 = (phy_data & VSC8211_AUX_SPEED_MASK) == + VSC8211_AUX_SPEED_IS_1000; + break; + case VSC8601_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, VSC8601_AUX_CTRL_STS, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read register VSC8601_AUX_CTRL_STS\n"); + return ret_val; + } + *is1000 = (phy_data & VSC8601_AUX_SPEED_MASK) == + VSC8601_AUX_SPEED_IS_1000; + break; + + case NON_PHY_PORT: + *is1000 = true; + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + if (!hw || !is1000) + return -1; + *is1000 = false; + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_is_speed_100 + * @hw e1000_hw struct containing device specific information + * @is100 a boolean returning true if phy is running at 100 + * + * This is called as part of e1000_config_mac_to_phy() to align + * the MAC with the PHY. It turns out on ICP_XXXX, this is not + * done automagically. + **/ +int32_t e1000_oem_phy_is_speed_100(struct e1000_hw *hw, int *is100) +{ +#ifdef EXTERNAL_MDIO + + uint16_t phy_data; + int32_t ret_val; + + if (!hw || !is100) + return -1; + + e_dbg("%s", __func__); + + /* + * Make note that the M88 phy is what'll be used on Truxton + * see e1000_config_mac_to_phy + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + ret_val = e1000_oem_read_phy_reg_ex(hw, + M88E1000_PHY_SPEC_STATUS, + &phy_data); + if (ret_val) { + e_dbg("Unable to read register " + "M88E1000_PHY_SPEC_STATUS\n"); + return ret_val; + } + *is100 = + (phy_data & M88E1000_PSSR_SPEED) == M88E1000_PSSR_100MBS; + break; + case VSC8211_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, VSC8211_AUX_CTRL_STS, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read register VSC8211_AUX_CTRL_STS\n"); + return ret_val; + } + *is100 = (phy_data & VSC8211_AUX_SPEED_MASK) == + VSC8211_AUX_SPEED_IS_100; + break; + case VSC8601_E_PHY_ID: + ret_val = + e1000_oem_read_phy_reg_ex(hw, VSC8601_AUX_CTRL_STS, + &phy_data); + if (ret_val) { + e_dbg + ("Unable to read register VSC8601_AUX_CTRL_STS\n"); + return ret_val; + } + *is100 = (phy_data & VSC8601_AUX_SPEED_MASK) == + VSC8601_AUX_SPEED_IS_100; + break; + case NON_PHY_PORT: + *is100 = false; + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + if (!hw || !is100) + return -1; + *is100 = false; + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_get_info + * @hw struct e1000_hw containing hardware specific data + * @phy_info struct e1000_phy_info that returned + * + * This is called by e1000_phy_get_info to gather PHY specific + * data. This is called for copper media based phys. + **/ +int32_t +e1000_oem_phy_get_info(struct e1000_hw *hw, struct e1000_phy_info *phy_info) +{ +#ifdef EXTERNAL_MDIO + + int32_t ret_val; + uint16_t phy_data, polarity; + + if (!hw || !phy_info) + return -1; + + e_dbg("%s", __func__); + + /* + * Make note that the M88 phy is what'll be used on Truxton + * see e1000_phy_m88_get_info + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + /* The downshift status is checked only once, after link is + * established and it stored in the hw->speed_downgraded + * parameter.*/ + phy_info->downshift = (e1000_downshift) hw->speed_downgraded; + + ret_val = e1000_oem_read_phy_reg_ex(hw, M88E1000_PHY_SPEC_CTRL, + &phy_data); + if (ret_val) { + e_dbg("Unable to read register " + "M88E1000_PHY_SPEC_CTRL\n"); + return ret_val; + } + + phy_info->extended_10bt_distance = + (phy_data & M88E1000_PSCR_10BT_EXT_DIST_ENABLE) + >> M88E1000_PSCR_10BT_EXT_DIST_ENABLE_SHIFT; + phy_info->polarity_correction = + (phy_data & M88E1000_PSCR_POLARITY_REVERSAL) + >> M88E1000_PSCR_POLARITY_REVERSAL_SHIFT; + + /* Check polarity status */ + ret_val = e1000_oem_check_polarity(hw, &polarity); + if (ret_val) + return ret_val; + + phy_info->cable_polarity = polarity; + + ret_val = + e1000_oem_read_phy_reg_ex(hw, M88E1000_PHY_SPEC_STATUS, + &phy_data); + if (ret_val) { + e_dbg("Unable to read register " + "M88E1000_PHY_SPEC_STATUS\n"); + return ret_val; + } + + phy_info->mdix_mode = (phy_data & M88E1000_PSSR_MDIX) + >> M88E1000_PSSR_MDIX_SHIFT; + + if ((phy_data & M88E1000_PSSR_SPEED) == M88E1000_PSSR_1000MBS) { + /* Cable Length Estimation and Local/Remote Receiver + * Information are only valid at 1000 Mbps. + */ + phy_info->cable_length = + (phy_data & M88E1000_PSSR_CABLE_LENGTH) + >> M88E1000_PSSR_CABLE_LENGTH_SHIFT; + + ret_val = + e1000_oem_read_phy_reg_ex(hw, PHY_1000T_STATUS, + &phy_data); + if (ret_val) { + e_dbg("Unable to read register " + "PHY_1000T_STATUS\n"); + return ret_val; + } + + phy_info->local_rx = + (phy_data & SR_1000T_LOCAL_RX_STATUS) + >> SR_1000T_LOCAL_RX_STATUS_SHIFT; + + phy_info->remote_rx = + (phy_data & SR_1000T_REMOTE_RX_STATUS) + >> SR_1000T_REMOTE_RX_STATUS_SHIFT; + } + + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_hw_reset + * @hw struct e1000_hw containing hardware specific data + * + * This function will perform a software initiated reset of + * the PHY + **/ +int32_t e1000_oem_phy_hw_reset(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + int32_t ret_val; + uint16_t phy_data; + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + /* + * This code pretty much copies the default case from + * e1000_phy_reset() as that is what is appropriate for + * the M88 used in truxton. + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + ret_val = e1000_oem_read_phy_reg_ex(hw, PHY_CTRL, &phy_data); + if (ret_val) { + e_dbg("Unable to read register PHY_CTRL\n"); + return ret_val; + } + + phy_data |= MII_CR_RESET; + ret_val = e1000_oem_write_phy_reg_ex(hw, PHY_CTRL, phy_data); + if (ret_val) { + e_dbg("Unable to write register PHY_CTRL\n"); + return ret_val; + } + + udelay(1); + break; + case NON_PHY_PORT: + /* do nothing */ + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_init_script + * @hw struct e1000_hw containing hardware specific data + * + * This gets called in three places, after egbe_oem_phy_hw_reset() + * to perform and post reset initialiation. Not all PHYs require + * this, which is why it was split off as a seperate function. + **/ +void e1000_oem_phy_init_script(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + if (!hw) + return; + + e_dbg("%s", __func__); + + /* call the GCU func that can do any phy specific init + * functions after a reset + * + * Make note that the M88 phy is what'll be used on Truxton + * + * The closest thing is in e1000_phy_init_script, however this is + * for the IGP style of phy. This is probably a no-op for truxton + * but may be needed by OEM's later on + * + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + case NON_PHY_PORT: + e_dbg("Nothing to do for OEM PHY Init"); + break; + default: + e_dbg("Invalid PHY ID\n"); + return; + } + +#endif /* ifdef EXTERNAL_MDIO */ + return; + +} + +/** + * e1000_oem_read_phy_reg_ex + * @hw struct e1000_hw containing hardware specific data + * @reg_addr address location within the PHY register set + * @phy_data returns the data read from reg_addr + * + * This encapsulates the interface call to the GCU for access + * to the MDIO for the PHY. + **/ +int32_t +e1000_oem_read_phy_reg_ex(struct e1000_hw *hw, + uint32_t reg_addr, uint16_t *phy_data) +{ +#ifdef EXTERNAL_MDIO + + int32_t ret_val; + + if (!hw || !phy_data) + return -1; + + e_dbg("%s", __func__); + + /* call the GCU func that will read the phy + * + * Make note that the M88 phy is what'll be used on Truxton. + * + * The closest thing is in e1000_read_phy_reg_ex. + * + * NOTE: this is 1 (of 2) functions that is truly dependant on the + * gcu module + */ + + ret_val = gcu_read_eth_phy(e1000_oem_get_phy_dev_number(hw), + reg_addr, phy_data); + if (ret_val) { + e_dbg("Error reading GCU"); + return ret_val; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_set_trans_gasket + * @hw: e1000_hw struct containing device specific information + * + * Returns E1000_SUCCESS, negative E1000 error code on failure + * + * This is called from e1000_config_mac_to_phy. Various supported + * Phys may require the RGMII/RMII Translation gasket be set to RMII. + **/ +int32_t e1000_oem_set_trans_gasket(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + if (!hw) + return -1; + + e_dbg("%s", __func__); + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + /* Gasket set correctly for Marvell Phys, so nothing to do */ + break; +#if 0 + /* Add your PHY_ID here if your device requires an RMII + * interface */ + case YOUR_PHY_ID: + ctrl_aux_reg = E1000_READ_REG(hw, CTRL_AUX); + /* Set the RGMII_RMII bit */ + ctrl_aux_reg |= E1000_CTRL_AUX_ICP_xxxx_MII_TGS; + E1000_WRITE_REG(hw, CTRL_AUX, ctrl_aux_reg); + break; +#endif + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_write_phy_reg_ex + * @hw struct e1000_hw containing hardware specific data + * @reg_addr address location within the PHY register set + * @phy_data data to be written to reg_addr + * + * This encapsulates the interface call to the GCU for access + * to the MDIO for the PHY. + **/ +int32_t +e1000_oem_write_phy_reg_ex(struct e1000_hw *hw, + uint32_t reg_addr, uint16_t phy_data) +{ +#ifdef EXTERNAL_MDIO + + int32_t ret_val; + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + /* call the GCU func that will write to the phy + * + * Make note that the M88 phy is what'll be used on Truxton. + * + * The closest thing is in e1000_write_phy_reg_ex + * + * NOTE: this is 2 (of 2) functions that is truly dependant on the + * gcu module + */ + ret_val = gcu_write_eth_phy(e1000_oem_get_phy_dev_number(hw), + reg_addr, phy_data); + if (ret_val) { + e_dbg("Error writing to GCU"); + return ret_val; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_needs_reset_with_mac + * @hw struct e1000_hw hardware specific data + * + * e1000_reset_hw is called to reset the MAC. If, for + * some reason the PHY needs to be reset as well, this + * should return true and then e1000_oem_phy_hw_reset() + * will be called. + **/ +int e1000_oem_phy_needs_reset_with_mac(struct e1000_hw *hw) +{ +#ifdef EXTERNAL_MDIO + + int ret_val; + + if (!hw) + return false; + + e_dbg("%s", __func__); + + /* + * From the original e1000 driver, the M88 + * PHYs did not seem to need this reset, + * so returning false. + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + case NON_PHY_PORT: + ret_val = false; + break; + default: + e_dbg("Invalid PHY ID\n"); + return false; + } + + return ret_val; + +#else /* ifdef EXTERNAL_MDIO */ + + return false; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_config_dsp_after_link_change + * @hw struct e1000_hw containing hardware specific data + * @link_up allows different configurations based on whether + * not the link was up. + * + * This is called from e1000_check_for_link, and allows for + * tweaking of the PHY, for PHYs that support a DSP. + * + **/ +int32_t +e1000_oem_config_dsp_after_link_change(struct e1000_hw *hw, int link_up) +{ +#ifdef EXTERNAL_MDIO + + if (!hw) + return -1; + + e_dbg("%s", __func__); + + /* + * Make note that the M88 phy is what'll be used on Truxton, + * but in the e1000 driver, it had no such func. This is a no-op + * for M88, but may be useful for other phys + * + * use e1000_config_dsp_after_link_change as example + */ + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + e_dbg("No DSP to configure on OEM PHY"); + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_get_cable_length + * @hw struct e1000_hw containing hardware specific data + * @min_length pointer to return the approx minimum length + * @max_length pointer to return the approx maximum length + * + * + **/ +int32_t +e1000_oem_get_cable_length(struct e1000_hw *hw, + uint16_t *min_length, uint16_t *max_length) +{ +#ifdef EXTERNAL_MDIO + + int32_t ret_val; + uint16_t cable_length; + uint16_t phy_data; + + if (!hw || !min_length || !max_length) + return -1; + + e_dbg("%s", __func__); + + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + ret_val = e1000_oem_read_phy_reg_ex(hw, + M88E1000_PHY_SPEC_STATUS, + &phy_data); + if (ret_val) + return ret_val; + + cable_length = (phy_data & M88E1000_PSSR_CABLE_LENGTH) + >> M88E1000_PSSR_CABLE_LENGTH_SHIFT; + + /* Convert the enum value to ranged values */ + switch (cable_length) { + case e1000_cable_length_50: + *min_length = 0; + *max_length = e1000_igp_cable_length_50; + break; + case e1000_cable_length_50_80: + *min_length = e1000_igp_cable_length_50; + *max_length = e1000_igp_cable_length_80; + break; + case e1000_cable_length_80_110: + *min_length = e1000_igp_cable_length_80; + *max_length = e1000_igp_cable_length_110; + break; + case e1000_cable_length_110_140: + *min_length = e1000_igp_cable_length_110; + *max_length = e1000_igp_cable_length_140; + break; + case e1000_cable_length_140: + *min_length = e1000_igp_cable_length_140; + *max_length = e1000_igp_cable_length_170; + break; + default: + return -E1000_ERR_PHY; + break; + } + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} + +/** + * e1000_oem_phy_is_link_up + * @hw e1000_hw struct containing device specific information + * @isUp a boolean returning true if link is up + * + * This is called as part of e1000_config_mac_to_phy() to align + * the MAC with the PHY. It turns out on ICP_XXXX, this is not + * done automagically. + **/ +int32_t e1000_oem_phy_is_link_up(struct e1000_hw *hw, int *isUp) +{ +#ifdef EXTERNAL_MDIO + + uint16_t phy_data; + uint16_t statusMask; + int32_t ret_val; + + if (!hw || !isUp) + return -1; + + e_dbg("%s", __func__); + + /* + * Make note that the M88 phy is what'll be used on Truxton + * see e1000_config_mac_to_phy + */ + + if (hw->phy_id == NON_PHY_PORT) { + *isUp = 1; + } else { + switch (hw->phy_id) { + case M88E1000_I_PHY_ID: + case M88E1141_E_PHY_ID: + e1000_oem_read_phy_reg_ex(hw, M88E1000_PHY_SPEC_STATUS, + &phy_data); + ret_val = + e1000_oem_read_phy_reg_ex(hw, + M88E1000_PHY_SPEC_STATUS, + &phy_data); + statusMask = M88E1000_PSSR_LINK; + break; + case VSC8211_E_PHY_ID: + case VSC8601_E_PHY_ID: + ret_val = e1000_oem_read_phy_reg_ex(hw, PHY_STATUS, + &phy_data); + statusMask = MII_SR_LINK_STATUS; + break; + default: + e_dbg("Invalid PHY ID\n"); + return -E1000_ERR_PHY_TYPE; + } + + if (ret_val) { + e_dbg("Unable to read PHY register\n"); + return ret_val; + } + + *isUp = (phy_data & statusMask) != 0; + } + + return E1000_SUCCESS; + +#else /* ifdef EXTERNAL_MDIO */ + + if (!hw || !isFD) + return -1; + *isUp = false; + return -E1000_ERR_PHY_TYPE; + +#endif /* ifdef EXTERNAL_MDIO */ +} |