diff options
Diffstat (limited to 'drivers/net/ethernet/intel/igc/igc_phy.c')
| -rw-r--r-- | drivers/net/ethernet/intel/igc/igc_phy.c | 791 | 
1 files changed, 791 insertions, 0 deletions
| diff --git a/drivers/net/ethernet/intel/igc/igc_phy.c b/drivers/net/ethernet/intel/igc/igc_phy.c new file mode 100644 index 000000000000..38e43e6fc1c7 --- /dev/null +++ b/drivers/net/ethernet/intel/igc/igc_phy.c @@ -0,0 +1,791 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c)  2018 Intel Corporation */ + +#include "igc_phy.h" + +/* forward declaration */ +static s32 igc_phy_setup_autoneg(struct igc_hw *hw); +static s32 igc_wait_autoneg(struct igc_hw *hw); + +/** + * igc_check_reset_block - Check if PHY reset is blocked + * @hw: pointer to the HW structure + * + * Read the PHY management control register and check whether a PHY reset + * is blocked.  If a reset is not blocked return 0, otherwise + * return IGC_ERR_BLK_PHY_RESET (12). + */ +s32 igc_check_reset_block(struct igc_hw *hw) +{ +	u32 manc; + +	manc = rd32(IGC_MANC); + +	return (manc & IGC_MANC_BLK_PHY_RST_ON_IDE) ? +		IGC_ERR_BLK_PHY_RESET : 0; +} + +/** + * igc_get_phy_id - Retrieve the PHY ID and revision + * @hw: pointer to the HW structure + * + * Reads the PHY registers and stores the PHY ID and possibly the PHY + * revision in the hardware structure. + */ +s32 igc_get_phy_id(struct igc_hw *hw) +{ +	struct igc_phy_info *phy = &hw->phy; +	s32 ret_val = 0; +	u16 phy_id; + +	ret_val = phy->ops.read_reg(hw, PHY_ID1, &phy_id); +	if (ret_val) +		goto out; + +	phy->id = (u32)(phy_id << 16); +	usleep_range(200, 500); +	ret_val = phy->ops.read_reg(hw, PHY_ID2, &phy_id); +	if (ret_val) +		goto out; + +	phy->id |= (u32)(phy_id & PHY_REVISION_MASK); +	phy->revision = (u32)(phy_id & ~PHY_REVISION_MASK); + +out: +	return ret_val; +} + +/** + * igc_phy_has_link - Polls PHY for link + * @hw: pointer to the HW structure + * @iterations: number of times to poll for link + * @usec_interval: delay between polling attempts + * @success: pointer to whether polling was successful or not + * + * Polls the PHY status register for link, 'iterations' number of times. + */ +s32 igc_phy_has_link(struct igc_hw *hw, u32 iterations, +		     u32 usec_interval, bool *success) +{ +	u16 i, phy_status; +	s32 ret_val = 0; + +	for (i = 0; i < iterations; i++) { +		/* Some PHYs require the PHY_STATUS register to be read +		 * twice due to the link bit being sticky.  No harm doing +		 * it across the board. +		 */ +		ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &phy_status); +		if (ret_val && usec_interval > 0) { +			/* If the first read fails, another entity may have +			 * ownership of the resources, wait and try again to +			 * see if they have relinquished the resources yet. +			 */ +			if (usec_interval >= 1000) +				mdelay(usec_interval / 1000); +			else +				udelay(usec_interval); +		} +		ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &phy_status); +		if (ret_val) +			break; +		if (phy_status & MII_SR_LINK_STATUS) +			break; +		if (usec_interval >= 1000) +			mdelay(usec_interval / 1000); +		else +			udelay(usec_interval); +	} + +	*success = (i < iterations) ? true : false; + +	return ret_val; +} + +/** + * igc_power_up_phy_copper - Restore copper link in case of PHY power down + * @hw: pointer to the HW structure + * + * In the case of a PHY power down to save power, or to turn off link during a + * driver unload, restore the link to previous settings. + */ +void igc_power_up_phy_copper(struct igc_hw *hw) +{ +	u16 mii_reg = 0; + +	/* The PHY will retain its settings across a power down/up cycle */ +	hw->phy.ops.read_reg(hw, PHY_CONTROL, &mii_reg); +	mii_reg &= ~MII_CR_POWER_DOWN; +	hw->phy.ops.write_reg(hw, PHY_CONTROL, mii_reg); +} + +/** + * igc_power_down_phy_copper - Power down copper PHY + * @hw: pointer to the HW structure + * + * Power down PHY to save power when interface is down and wake on lan + * is not enabled. + */ +void igc_power_down_phy_copper(struct igc_hw *hw) +{ +	u16 mii_reg = 0; + +	/* The PHY will retain its settings across a power down/up cycle */ +	hw->phy.ops.read_reg(hw, PHY_CONTROL, &mii_reg); +	mii_reg |= MII_CR_POWER_DOWN; + +	/* Temporary workaround - should be removed when PHY will implement +	 * IEEE registers as properly +	 */ +	/* hw->phy.ops.write_reg(hw, PHY_CONTROL, mii_reg);*/ +	usleep_range(1000, 2000); +} + +/** + * igc_check_downshift - Checks whether a downshift in speed occurred + * @hw: pointer to the HW structure + * + * Success returns 0, Failure returns 1 + * + * A downshift is detected by querying the PHY link health. + */ +s32 igc_check_downshift(struct igc_hw *hw) +{ +	struct igc_phy_info *phy = &hw->phy; +	u16 phy_data, offset, mask; +	s32 ret_val; + +	switch (phy->type) { +	case igc_phy_i225: +	default: +		/* speed downshift not supported */ +		phy->speed_downgraded = false; +		ret_val = 0; +		goto out; +	} + +	ret_val = phy->ops.read_reg(hw, offset, &phy_data); + +	if (!ret_val) +		phy->speed_downgraded = (phy_data & mask) ? true : false; + +out: +	return ret_val; +} + +/** + * igc_phy_hw_reset - PHY hardware reset + * @hw: pointer to the HW structure + * + * Verify the reset block is not blocking us from resetting.  Acquire + * semaphore (if necessary) and read/set/write the device control reset + * bit in the PHY.  Wait the appropriate delay time for the device to + * reset and release the semaphore (if necessary). + */ +s32 igc_phy_hw_reset(struct igc_hw *hw) +{ +	struct igc_phy_info *phy = &hw->phy; +	s32  ret_val; +	u32 ctrl; + +	ret_val = igc_check_reset_block(hw); +	if (ret_val) { +		ret_val = 0; +		goto out; +	} + +	ret_val = phy->ops.acquire(hw); +	if (ret_val) +		goto out; + +	ctrl = rd32(IGC_CTRL); +	wr32(IGC_CTRL, ctrl | IGC_CTRL_PHY_RST); +	wrfl(); + +	udelay(phy->reset_delay_us); + +	wr32(IGC_CTRL, ctrl); +	wrfl(); + +	usleep_range(1500, 2000); + +	phy->ops.release(hw); + +out: +	return ret_val; +} + +/** + * igc_copper_link_autoneg - Setup/Enable autoneg for copper link + * @hw: pointer to the HW structure + * + * Performs initial bounds checking on autoneg advertisement parameter, then + * configure to advertise the full capability.  Setup the PHY to autoneg + * and restart the negotiation process between the link partner.  If + * autoneg_wait_to_complete, then wait for autoneg to complete before exiting. + */ +static s32 igc_copper_link_autoneg(struct igc_hw *hw) +{ +	struct igc_phy_info *phy = &hw->phy; +	u16 phy_ctrl; +	s32 ret_val; + +	/* Perform some bounds checking on the autoneg advertisement +	 * parameter. +	 */ +	phy->autoneg_advertised &= phy->autoneg_mask; + +	/* If autoneg_advertised is zero, we assume it was not defaulted +	 * by the calling code so we set to advertise full capability. +	 */ +	if (phy->autoneg_advertised == 0) +		phy->autoneg_advertised = phy->autoneg_mask; + +	hw_dbg("Reconfiguring auto-neg advertisement params\n"); +	ret_val = igc_phy_setup_autoneg(hw); +	if (ret_val) { +		hw_dbg("Error Setting up Auto-Negotiation\n"); +		goto out; +	} +	hw_dbg("Restarting Auto-Neg\n"); + +	/* Restart auto-negotiation by setting the Auto Neg Enable bit and +	 * the Auto Neg Restart bit in the PHY control register. +	 */ +	ret_val = phy->ops.read_reg(hw, PHY_CONTROL, &phy_ctrl); +	if (ret_val) +		goto out; + +	phy_ctrl |= (MII_CR_AUTO_NEG_EN | MII_CR_RESTART_AUTO_NEG); +	ret_val = phy->ops.write_reg(hw, PHY_CONTROL, phy_ctrl); +	if (ret_val) +		goto out; + +	/* Does the user want to wait for Auto-Neg to complete here, or +	 * check at a later time (for example, callback routine). +	 */ +	if (phy->autoneg_wait_to_complete) { +		ret_val = igc_wait_autoneg(hw); +		if (ret_val) { +			hw_dbg("Error while waiting for autoneg to complete\n"); +			goto out; +		} +	} + +	hw->mac.get_link_status = true; + +out: +	return ret_val; +} + +/** + * igc_wait_autoneg - Wait for auto-neg completion + * @hw: pointer to the HW structure + * + * Waits for auto-negotiation to complete or for the auto-negotiation time + * limit to expire, which ever happens first. + */ +static s32 igc_wait_autoneg(struct igc_hw *hw) +{ +	u16 i, phy_status; +	s32 ret_val = 0; + +	/* Break after autoneg completes or PHY_AUTO_NEG_LIMIT expires. */ +	for (i = PHY_AUTO_NEG_LIMIT; i > 0; i--) { +		ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &phy_status); +		if (ret_val) +			break; +		ret_val = hw->phy.ops.read_reg(hw, PHY_STATUS, &phy_status); +		if (ret_val) +			break; +		if (phy_status & MII_SR_AUTONEG_COMPLETE) +			break; +		msleep(100); +	} + +	/* PHY_AUTO_NEG_TIME expiration doesn't guarantee auto-negotiation +	 * has completed. +	 */ +	return ret_val; +} + +/** + * igc_phy_setup_autoneg - Configure PHY for auto-negotiation + * @hw: pointer to the HW structure + * + * Reads the MII auto-neg advertisement register and/or the 1000T control + * register and if the PHY is already setup for auto-negotiation, then + * return successful.  Otherwise, setup advertisement and flow control to + * the appropriate values for the wanted auto-negotiation. + */ +static s32 igc_phy_setup_autoneg(struct igc_hw *hw) +{ +	struct igc_phy_info *phy = &hw->phy; +	u16 aneg_multigbt_an_ctrl = 0; +	u16 mii_1000t_ctrl_reg = 0; +	u16 mii_autoneg_adv_reg; +	s32 ret_val; + +	phy->autoneg_advertised &= phy->autoneg_mask; + +	/* Read the MII Auto-Neg Advertisement Register (Address 4). */ +	ret_val = phy->ops.read_reg(hw, PHY_AUTONEG_ADV, &mii_autoneg_adv_reg); +	if (ret_val) +		return ret_val; + +	if (phy->autoneg_mask & ADVERTISE_1000_FULL) { +		/* Read the MII 1000Base-T Control Register (Address 9). */ +		ret_val = phy->ops.read_reg(hw, PHY_1000T_CTRL, +					    &mii_1000t_ctrl_reg); +		if (ret_val) +			return ret_val; +	} + +	if ((phy->autoneg_mask & ADVERTISE_2500_FULL) && +	    hw->phy.id == I225_I_PHY_ID) { +		/* Read the MULTI GBT AN Control Register - reg 7.32 */ +		ret_val = phy->ops.read_reg(hw, (STANDARD_AN_REG_MASK << +					    MMD_DEVADDR_SHIFT) | +					    ANEG_MULTIGBT_AN_CTRL, +					    &aneg_multigbt_an_ctrl); + +		if (ret_val) +			return ret_val; +	} + +	/* Need to parse both autoneg_advertised and fc and set up +	 * the appropriate PHY registers.  First we will parse for +	 * autoneg_advertised software override.  Since we can advertise +	 * a plethora of combinations, we need to check each bit +	 * individually. +	 */ + +	/* First we clear all the 10/100 mb speed bits in the Auto-Neg +	 * Advertisement Register (Address 4) and the 1000 mb speed bits in +	 * the  1000Base-T Control Register (Address 9). +	 */ +	mii_autoneg_adv_reg &= ~(NWAY_AR_100TX_FD_CAPS | +				 NWAY_AR_100TX_HD_CAPS | +				 NWAY_AR_10T_FD_CAPS   | +				 NWAY_AR_10T_HD_CAPS); +	mii_1000t_ctrl_reg &= ~(CR_1000T_HD_CAPS | CR_1000T_FD_CAPS); + +	hw_dbg("autoneg_advertised %x\n", phy->autoneg_advertised); + +	/* Do we want to advertise 10 Mb Half Duplex? */ +	if (phy->autoneg_advertised & ADVERTISE_10_HALF) { +		hw_dbg("Advertise 10mb Half duplex\n"); +		mii_autoneg_adv_reg |= NWAY_AR_10T_HD_CAPS; +	} + +	/* Do we want to advertise 10 Mb Full Duplex? */ +	if (phy->autoneg_advertised & ADVERTISE_10_FULL) { +		hw_dbg("Advertise 10mb Full duplex\n"); +		mii_autoneg_adv_reg |= NWAY_AR_10T_FD_CAPS; +	} + +	/* Do we want to advertise 100 Mb Half Duplex? */ +	if (phy->autoneg_advertised & ADVERTISE_100_HALF) { +		hw_dbg("Advertise 100mb Half duplex\n"); +		mii_autoneg_adv_reg |= NWAY_AR_100TX_HD_CAPS; +	} + +	/* Do we want to advertise 100 Mb Full Duplex? */ +	if (phy->autoneg_advertised & ADVERTISE_100_FULL) { +		hw_dbg("Advertise 100mb Full duplex\n"); +		mii_autoneg_adv_reg |= NWAY_AR_100TX_FD_CAPS; +	} + +	/* We do not allow the Phy to advertise 1000 Mb Half Duplex */ +	if (phy->autoneg_advertised & ADVERTISE_1000_HALF) +		hw_dbg("Advertise 1000mb Half duplex request denied!\n"); + +	/* Do we want to advertise 1000 Mb Full Duplex? */ +	if (phy->autoneg_advertised & ADVERTISE_1000_FULL) { +		hw_dbg("Advertise 1000mb Full duplex\n"); +		mii_1000t_ctrl_reg |= CR_1000T_FD_CAPS; +	} + +	/* We do not allow the Phy to advertise 2500 Mb Half Duplex */ +	if (phy->autoneg_advertised & ADVERTISE_2500_HALF) +		hw_dbg("Advertise 2500mb Half duplex request denied!\n"); + +	/* Do we want to advertise 2500 Mb Full Duplex? */ +	if (phy->autoneg_advertised & ADVERTISE_2500_FULL) { +		hw_dbg("Advertise 2500mb Full duplex\n"); +		aneg_multigbt_an_ctrl |= CR_2500T_FD_CAPS; +	} else { +		aneg_multigbt_an_ctrl &= ~CR_2500T_FD_CAPS; +	} + +	/* Check for a software override of the flow control settings, and +	 * setup the PHY advertisement registers accordingly.  If +	 * auto-negotiation is enabled, then software will have to set the +	 * "PAUSE" bits to the correct value in the Auto-Negotiation +	 * Advertisement Register (PHY_AUTONEG_ADV) and re-start auto- +	 * negotiation. +	 * +	 * The possible values of the "fc" parameter are: +	 *      0:  Flow control is completely disabled +	 *      1:  Rx flow control is enabled (we can receive pause frames +	 *          but not send pause frames). +	 *      2:  Tx flow control is enabled (we can send pause frames +	 *          but we do not support receiving pause frames). +	 *      3:  Both Rx and Tx flow control (symmetric) are enabled. +	 *  other:  No software override.  The flow control configuration +	 *          in the EEPROM is used. +	 */ +	switch (hw->fc.current_mode) { +	case igc_fc_none: +		/* Flow control (Rx & Tx) is completely disabled by a +		 * software over-ride. +		 */ +		mii_autoneg_adv_reg &= ~(NWAY_AR_ASM_DIR | NWAY_AR_PAUSE); +		break; +	case igc_fc_rx_pause: +		/* Rx Flow control is enabled, and Tx Flow control is +		 * disabled, by a software over-ride. +		 * +		 * Since there really isn't a way to advertise that we are +		 * capable of Rx Pause ONLY, we will advertise that we +		 * support both symmetric and asymmetric Rx PAUSE.  Later +		 * (in igc_config_fc_after_link_up) we will disable the +		 * hw's ability to send PAUSE frames. +		 */ +		mii_autoneg_adv_reg |= (NWAY_AR_ASM_DIR | NWAY_AR_PAUSE); +		break; +	case igc_fc_tx_pause: +		/* Tx Flow control is enabled, and Rx Flow control is +		 * disabled, by a software over-ride. +		 */ +		mii_autoneg_adv_reg |= NWAY_AR_ASM_DIR; +		mii_autoneg_adv_reg &= ~NWAY_AR_PAUSE; +		break; +	case igc_fc_full: +		/* Flow control (both Rx and Tx) is enabled by a software +		 * over-ride. +		 */ +		mii_autoneg_adv_reg |= (NWAY_AR_ASM_DIR | NWAY_AR_PAUSE); +		break; +	default: +		hw_dbg("Flow control param set incorrectly\n"); +		return -IGC_ERR_CONFIG; +	} + +	ret_val = phy->ops.write_reg(hw, PHY_AUTONEG_ADV, mii_autoneg_adv_reg); +	if (ret_val) +		return ret_val; + +	hw_dbg("Auto-Neg Advertising %x\n", mii_autoneg_adv_reg); + +	if (phy->autoneg_mask & ADVERTISE_1000_FULL) +		ret_val = phy->ops.write_reg(hw, PHY_1000T_CTRL, +					     mii_1000t_ctrl_reg); + +	if ((phy->autoneg_mask & ADVERTISE_2500_FULL) && +	    hw->phy.id == I225_I_PHY_ID) +		ret_val = phy->ops.write_reg(hw, +					     (STANDARD_AN_REG_MASK << +					     MMD_DEVADDR_SHIFT) | +					     ANEG_MULTIGBT_AN_CTRL, +					     aneg_multigbt_an_ctrl); + +	return ret_val; +} + +/** + * igc_setup_copper_link - Configure copper link settings + * @hw: pointer to the HW structure + * + * Calls the appropriate function to configure the link for auto-neg or forced + * speed and duplex.  Then we check for link, once link is established calls + * to configure collision distance and flow control are called.  If link is + * not established, we return -IGC_ERR_PHY (-2). + */ +s32 igc_setup_copper_link(struct igc_hw *hw) +{ +	s32 ret_val = 0; +	bool link; + +	if (hw->mac.autoneg) { +		/* Setup autoneg and flow control advertisement and perform +		 * autonegotiation. +		 */ +		ret_val = igc_copper_link_autoneg(hw); +		if (ret_val) +			goto out; +	} else { +		/* PHY will be set to 10H, 10F, 100H or 100F +		 * depending on user settings. +		 */ +		hw_dbg("Forcing Speed and Duplex\n"); +		ret_val = hw->phy.ops.force_speed_duplex(hw); +		if (ret_val) { +			hw_dbg("Error Forcing Speed and Duplex\n"); +			goto out; +		} +	} + +	/* Check link status. Wait up to 100 microseconds for link to become +	 * valid. +	 */ +	ret_val = igc_phy_has_link(hw, COPPER_LINK_UP_LIMIT, 10, &link); +	if (ret_val) +		goto out; + +	if (link) { +		hw_dbg("Valid link established!!!\n"); +		igc_config_collision_dist(hw); +		ret_val = igc_config_fc_after_link_up(hw); +	} else { +		hw_dbg("Unable to establish link!!!\n"); +	} + +out: +	return ret_val; +} + +/** + * igc_read_phy_reg_mdic - Read MDI control register + * @hw: pointer to the HW structure + * @offset: register offset to be read + * @data: pointer to the read data + * + * Reads the MDI control register in the PHY at offset and stores the + * information read to data. + */ +static s32 igc_read_phy_reg_mdic(struct igc_hw *hw, u32 offset, u16 *data) +{ +	struct igc_phy_info *phy = &hw->phy; +	u32 i, mdic = 0; +	s32 ret_val = 0; + +	if (offset > MAX_PHY_REG_ADDRESS) { +		hw_dbg("PHY Address %d is out of range\n", offset); +		ret_val = -IGC_ERR_PARAM; +		goto out; +	} + +	/* 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 << IGC_MDIC_REG_SHIFT) | +		(phy->addr << IGC_MDIC_PHY_SHIFT) | +		(IGC_MDIC_OP_READ)); + +	wr32(IGC_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 < IGC_GEN_POLL_TIMEOUT; i++) { +		usleep_range(500, 1000); +		mdic = rd32(IGC_MDIC); +		if (mdic & IGC_MDIC_READY) +			break; +	} +	if (!(mdic & IGC_MDIC_READY)) { +		hw_dbg("MDI Read did not complete\n"); +		ret_val = -IGC_ERR_PHY; +		goto out; +	} +	if (mdic & IGC_MDIC_ERROR) { +		hw_dbg("MDI Error\n"); +		ret_val = -IGC_ERR_PHY; +		goto out; +	} +	*data = (u16)mdic; + +out: +	return ret_val; +} + +/** + * igc_write_phy_reg_mdic - Write MDI control register + * @hw: pointer to the HW structure + * @offset: register offset to write to + * @data: data to write to register at offset + * + * Writes data to MDI control register in the PHY at offset. + */ +static s32 igc_write_phy_reg_mdic(struct igc_hw *hw, u32 offset, u16 data) +{ +	struct igc_phy_info *phy = &hw->phy; +	u32 i, mdic = 0; +	s32 ret_val = 0; + +	if (offset > MAX_PHY_REG_ADDRESS) { +		hw_dbg("PHY Address %d is out of range\n", offset); +		ret_val = -IGC_ERR_PARAM; +		goto out; +	} + +	/* 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 write the desired data. +	 */ +	mdic = (((u32)data) | +		(offset << IGC_MDIC_REG_SHIFT) | +		(phy->addr << IGC_MDIC_PHY_SHIFT) | +		(IGC_MDIC_OP_WRITE)); + +	wr32(IGC_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 < IGC_GEN_POLL_TIMEOUT; i++) { +		usleep_range(500, 1000); +		mdic = rd32(IGC_MDIC); +		if (mdic & IGC_MDIC_READY) +			break; +	} +	if (!(mdic & IGC_MDIC_READY)) { +		hw_dbg("MDI Write did not complete\n"); +		ret_val = -IGC_ERR_PHY; +		goto out; +	} +	if (mdic & IGC_MDIC_ERROR) { +		hw_dbg("MDI Error\n"); +		ret_val = -IGC_ERR_PHY; +		goto out; +	} + +out: +	return ret_val; +} + +/** + * __igc_access_xmdio_reg - Read/write XMDIO register + * @hw: pointer to the HW structure + * @address: XMDIO address to program + * @dev_addr: device address to program + * @data: pointer to value to read/write from/to the XMDIO address + * @read: boolean flag to indicate read or write + */ +static s32 __igc_access_xmdio_reg(struct igc_hw *hw, u16 address, +				  u8 dev_addr, u16 *data, bool read) +{ +	s32 ret_val; + +	ret_val = hw->phy.ops.write_reg(hw, IGC_MMDAC, dev_addr); +	if (ret_val) +		return ret_val; + +	ret_val = hw->phy.ops.write_reg(hw, IGC_MMDAAD, address); +	if (ret_val) +		return ret_val; + +	ret_val = hw->phy.ops.write_reg(hw, IGC_MMDAC, IGC_MMDAC_FUNC_DATA | +					dev_addr); +	if (ret_val) +		return ret_val; + +	if (read) +		ret_val = hw->phy.ops.read_reg(hw, IGC_MMDAAD, data); +	else +		ret_val = hw->phy.ops.write_reg(hw, IGC_MMDAAD, *data); +	if (ret_val) +		return ret_val; + +	/* Recalibrate the device back to 0 */ +	ret_val = hw->phy.ops.write_reg(hw, IGC_MMDAC, 0); +	if (ret_val) +		return ret_val; + +	return ret_val; +} + +/** + * igc_read_xmdio_reg - Read XMDIO register + * @hw: pointer to the HW structure + * @addr: XMDIO address to program + * @dev_addr: device address to program + * @data: value to be read from the EMI address + */ +static s32 igc_read_xmdio_reg(struct igc_hw *hw, u16 addr, +			      u8 dev_addr, u16 *data) +{ +	return __igc_access_xmdio_reg(hw, addr, dev_addr, data, true); +} + +/** + * igc_write_xmdio_reg - Write XMDIO register + * @hw: pointer to the HW structure + * @addr: XMDIO address to program + * @dev_addr: device address to program + * @data: value to be written to the XMDIO address + */ +static s32 igc_write_xmdio_reg(struct igc_hw *hw, u16 addr, +			       u8 dev_addr, u16 data) +{ +	return __igc_access_xmdio_reg(hw, addr, dev_addr, &data, false); +} + +/** + * igc_write_phy_reg_gpy - Write GPY PHY register + * @hw: pointer to the HW structure + * @offset: register offset to write to + * @data: data to write at register offset + * + * Acquires semaphore, if necessary, then writes the data to PHY register + * at the offset. Release any acquired semaphores before exiting. + */ +s32 igc_write_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 data) +{ +	u8 dev_addr = (offset & GPY_MMD_MASK) >> GPY_MMD_SHIFT; +	s32 ret_val; + +	offset = offset & GPY_REG_MASK; + +	if (!dev_addr) { +		ret_val = hw->phy.ops.acquire(hw); +		if (ret_val) +			return ret_val; +		ret_val = igc_write_phy_reg_mdic(hw, offset, data); +		if (ret_val) +			return ret_val; +		hw->phy.ops.release(hw); +	} else { +		ret_val = igc_write_xmdio_reg(hw, (u16)offset, dev_addr, +					      data); +	} + +	return ret_val; +} + +/** + * igc_read_phy_reg_gpy - Read GPY PHY register + * @hw: pointer to the HW structure + * @offset: lower half is register offset to read to + * upper half is MMD to use. + * @data: data to read at register offset + * + * Acquires semaphore, if necessary, then reads the data in the PHY register + * at the offset. Release any acquired semaphores before exiting. + */ +s32 igc_read_phy_reg_gpy(struct igc_hw *hw, u32 offset, u16 *data) +{ +	u8 dev_addr = (offset & GPY_MMD_MASK) >> GPY_MMD_SHIFT; +	s32 ret_val; + +	offset = offset & GPY_REG_MASK; + +	if (!dev_addr) { +		ret_val = hw->phy.ops.acquire(hw); +		if (ret_val) +			return ret_val; +		ret_val = igc_read_phy_reg_mdic(hw, offset, data); +		if (ret_val) +			return ret_val; +		hw->phy.ops.release(hw); +	} else { +		ret_val = igc_read_xmdio_reg(hw, (u16)offset, dev_addr, +					     data); +	} + +	return ret_val; +} |