/*
 * This file is provided under a CDDLv1 license.  When using or
 * redistributing this file, you may do so under this license.
 * In redistributing this file this license must be included
 * and no other modification of this header file is permitted.
 *
 * CDDL LICENSE SUMMARY
 *
 * Copyright(c) 1999 - 2009 Intel Corporation. All rights reserved.
 *
 * The contents of this file are subject to the terms of Version
 * 1.0 of the Common Development and Distribution License (the "License").
 *
 * You should have received a copy of the License with this software.
 * You can obtain a copy of the License at
 *	http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms of the CDDLv1.
 */
#include "e1000_api.h"

#define	E1000_FIFO_MULTIPLIER			0x80
#define	E1000_FIFO_HDR_SIZE			0x10
#define	E1000_FIFO_GRANULARITY			0x10
#define	E1000_FIFO_PAD_82547			0x3E0
#define	E1000_ERR_FIFO_WRAP			8

#define	DSP_RESET_ENABLE			0x0
#define	DSP_RESET_DISABLE			0x2
#define	E1000_MAX_DSP_RESETS			10

#define	E1000_ROUNDUP(size, unit)	(((size) + (unit) - 1) & ~((unit) - 1))


/*
 * e1000_ttl_workaround_enabled_82541 - Returns current TTL workaround status
 * @hw: pointer to the HW structure
 *
 * Returns the current status of the TTL workaround, as to whether the
 * workaround is enabled or disabled.
 */
bool
e1000_ttl_workaround_enabled_82541(struct e1000_hw *hw)
{
	struct e1000_dev_spec_82541 *dev_spec = &hw->dev_spec._82541;
	bool state = false;

	DEBUGFUNC("e1000_ttl_workaround_enabled_82541");

	if ((hw->mac.type != e1000_82541) && (hw->mac.type != e1000_82547))
		goto out;

	state = dev_spec->ttl_workaround;

out:
	return (state);
}

/*
 * e1000_fifo_workaround_82547 - Workaround for Tx fifo failure
 * @hw: pointer to the HW structure
 * @length: length of next outgoing frame
 *
 * Returns: E1000_ERR_FIFO_WRAP if the next packet cannot be transmitted yet
 *	E1000_SUCCESS if the next packet can be transmitted
 *
 * Workaround for the 82547 Tx fifo failure.
 */
s32
e1000_fifo_workaround_82547(struct e1000_hw *hw, u16 length)
{
	struct e1000_dev_spec_82541 *dev_spec = &hw->dev_spec._82541;
	u32 tctl;
	s32 ret_val = E1000_SUCCESS;
	u16 fifo_pkt_len;

	DEBUGFUNC("e1000_fifo_workaround_82547");

	if (hw->mac.type != e1000_82547)
		goto out;

	/*
	 * Get the length as seen by the FIFO of the next real
	 * packet to be transmitted.
	 */
	fifo_pkt_len = E1000_ROUNDUP(length + E1000_FIFO_HDR_SIZE,
	    E1000_FIFO_GRANULARITY);

	if (fifo_pkt_len <= (E1000_FIFO_PAD_82547 + E1000_FIFO_HDR_SIZE))
		goto out;

	if ((dev_spec->tx_fifo_head + fifo_pkt_len) <
	    (dev_spec->tx_fifo_size + E1000_FIFO_PAD_82547))
		goto out;

	if (E1000_READ_REG(hw, E1000_TDT(0)) !=
	    E1000_READ_REG(hw, E1000_TDH(0))) {
		ret_val = -E1000_ERR_FIFO_WRAP;
		goto out;
	}

	if (E1000_READ_REG(hw, E1000_TDFT) != E1000_READ_REG(hw, E1000_TDFH)) {
		ret_val = -E1000_ERR_FIFO_WRAP;
		goto out;
	}

	if (E1000_READ_REG(hw, E1000_TDFTS) !=
	    E1000_READ_REG(hw, E1000_TDFHS)) {
		ret_val = -E1000_ERR_FIFO_WRAP;
		goto out;
	}

	/* Disable the tx unit to avoid further pointer movement */
	tctl = E1000_READ_REG(hw, E1000_TCTL);
	E1000_WRITE_REG(hw, E1000_TCTL, tctl & ~E1000_TCTL_EN);

	/* Reset the fifo pointers. */
	E1000_WRITE_REG(hw, E1000_TDFT, dev_spec->tx_fifo_start);
	E1000_WRITE_REG(hw, E1000_TDFH, dev_spec->tx_fifo_start);
	E1000_WRITE_REG(hw, E1000_TDFTS, dev_spec->tx_fifo_start);
	E1000_WRITE_REG(hw, E1000_TDFHS, dev_spec->tx_fifo_start);

	/* Re-enabling tx unit */
	E1000_WRITE_REG(hw, E1000_TCTL, tctl);
	E1000_WRITE_FLUSH(hw);

	dev_spec->tx_fifo_head = 0;

out:
	return (ret_val);
}

/*
 * e1000_update_tx_fifo_head - Update Tx fifo head pointer
 * @hw: pointer to the HW structure
 * @length: length of next outgoing frame
 *
 * Updates the SW calculated Tx FIFO head pointer.
 */
void
e1000_update_tx_fifo_head_82547(struct e1000_hw *hw, u32 length)
{
	struct e1000_dev_spec_82541 *dev_spec = &hw->dev_spec._82541;

	DEBUGFUNC("e1000_update_tx_fifo_head_82547");

	if (hw->mac.type != e1000_82547)
		return;

	dev_spec->tx_fifo_head += E1000_ROUNDUP(length + E1000_FIFO_HDR_SIZE,
	    E1000_FIFO_GRANULARITY);

	if (dev_spec->tx_fifo_head > dev_spec->tx_fifo_size)
		dev_spec->tx_fifo_head -= dev_spec->tx_fifo_size;
}

/*
 * e1000_set_ttl_workaround_state_82541 - Enable/Disables TTL workaround
 * @hw: pointer to the HW structure
 * @state: boolean to enable/disable TTL workaround
 *
 * For 82541 or 82547 only silicon, allows the driver to enable/disable the
 * TTL workaround.
 */
void
e1000_set_ttl_workaround_state_82541(struct e1000_hw *hw, bool state)
{
	struct e1000_dev_spec_82541 *dev_spec = &hw->dev_spec._82541;

	DEBUGFUNC("e1000_set_ttl_workaround_state_82541");

	if ((hw->mac.type != e1000_82541) && (hw->mac.type != e1000_82547))
		return;

	dev_spec->ttl_workaround = state;
}

/*
 * e1000_igp_ttl_workaround_82547 - Workaround for long TTL on 100HD hubs
 * @hw: pointer to the HW structure
 *
 * Returns: E1000_ERR_PHY if fail to read/write the PHY
 *          E1000_SUCCESS in any other case
 *
 * This function, specific to 82547 hardware only, needs to be called every
 * second.  It checks if a parallel detect fault has occurred.  If a fault
 * occurred, disable/enable the DSP reset mechanism up to 5 times (once per
 * second).  If link is established, stop the workaround and ensure the DSP
 * reset is enabled.
 */
s32
e1000_igp_ttl_workaround_82547(struct e1000_hw *hw)
{
	struct e1000_dev_spec_82541 *dev_spec = &hw->dev_spec._82541;
	s32 ret_val = E1000_SUCCESS;
	u16 phy_data = 0;
	u16 dsp_value = DSP_RESET_ENABLE;
	bool link;

	DEBUGFUNC("e1000_igp_ttl_workaround_82547");

	/* The workaround needed only for B-0 silicon HW */
	if ((hw->mac.type != e1000_82541) && (hw->mac.type != e1000_82547))
		goto out;

	if (!(e1000_ttl_workaround_enabled_82541(hw)))
		goto out;

	/* Check for link first */
	ret_val = e1000_phy_has_link_generic(hw, 1, 0, &link);
	if (ret_val)
		goto out;

	if (link) {
		/*
		 * If link is established during the workaround,
		 * the DSP mechanism must be enabled.
		 */
		if (dev_spec->dsp_reset_counter) {
			dev_spec->dsp_reset_counter = 0;
			dsp_value = DSP_RESET_ENABLE;
		} else {
			ret_val = E1000_SUCCESS;
			goto out;
		}
	} else {
		if (dev_spec->dsp_reset_counter == 0) {
			/*
			 * Workaround not activated,
			 * check if it needs activation
			 */
			ret_val = hw->phy.ops.read_reg(hw,
			    PHY_AUTONEG_EXP,
			    &phy_data);
			if (ret_val)
				goto out;
			/*
			 * Activate the workaround if there was a
			 * parallel detect fault
			 */
			if (phy_data & NWAY_ER_PAR_DETECT_FAULT) {
				dev_spec->dsp_reset_counter++;
			} else {
				ret_val = E1000_SUCCESS;
				goto out;
			}
		}

		/* After 5 times, stop the workaround */
		if (dev_spec->dsp_reset_counter > E1000_MAX_DSP_RESETS) {
			dev_spec->dsp_reset_counter = 0;
			dsp_value = DSP_RESET_ENABLE;
		} else {
			if (dev_spec->dsp_reset_counter) {
				dsp_value = (dev_spec->dsp_reset_counter & 1)
				    ? DSP_RESET_DISABLE
				    : DSP_RESET_ENABLE;
				dev_spec->dsp_reset_counter++;
			}
		}
	}

	ret_val =
	    hw->phy.ops.write_reg(hw, IGP01E1000_PHY_DSP_RESET, dsp_value);

out:
	return (ret_val);
}