/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include "sys/bge_impl.h"

/*
 * Bit test macros, returning boolean_t values
 */
#define	BIS(w, b)	(((w) & (b)) ? B_TRUE : B_FALSE)
#define	BIC(w, b)	(((w) & (b)) ? B_FALSE : B_TRUE)
#define	UPORDOWN(x)	((x) ? "up" : "down")

/*
 * ========== Copper (PHY) support ==========
 */

#define	BGE_DBG		BGE_DBG_PHY	/* debug flag for this code	*/

/*
 * #defines:
 *	BGE_COPPER_WIRESPEED controls whether the Broadcom WireSpeed(tm)
 *	feature is enabled.  We need to recheck whether this can be
 *	enabled; at one time it seemed to interact unpleasantly with the
 *	loopback modes.
 *
 *	BGE_COPPER_IDLEOFF controls whether the (copper) PHY power is
 *	turned off when the PHY is idled i.e. during driver suspend().
 *	For now this is disabled because the chip doesn't seem to
 *	resume cleanly if the PHY power is turned off.
 */
#define	BGE_COPPER_WIRESPEED	B_TRUE
#define	BGE_COPPER_IDLEOFF	B_FALSE

/*
 * The arrays below can be indexed by the MODE bits from the Auxiliary
 * Status register to determine the current speed/duplex settings.
 */
static const int16_t bge_copper_link_speed[] = {
	0,				/* MII_AUX_STATUS_MODE_NONE	*/
	10,				/* MII_AUX_STATUS_MODE_10_H	*/
	10,				/* MII_AUX_STATUS_MODE_10_F	*/
	100,				/* MII_AUX_STATUS_MODE_100_H	*/
	0,				/* MII_AUX_STATUS_MODE_100_4	*/
	100,				/* MII_AUX_STATUS_MODE_100_F	*/
	1000,				/* MII_AUX_STATUS_MODE_1000_H	*/
	1000				/* MII_AUX_STATUS_MODE_1000_F	*/
};

static const int8_t bge_copper_link_duplex[] = {
	LINK_DUPLEX_UNKNOWN,		/* MII_AUX_STATUS_MODE_NONE	*/
	LINK_DUPLEX_HALF,		/* MII_AUX_STATUS_MODE_10_H	*/
	LINK_DUPLEX_FULL,		/* MII_AUX_STATUS_MODE_10_F	*/
	LINK_DUPLEX_HALF,		/* MII_AUX_STATUS_MODE_100_H	*/
	LINK_DUPLEX_UNKNOWN,		/* MII_AUX_STATUS_MODE_100_4	*/
	LINK_DUPLEX_FULL,		/* MII_AUX_STATUS_MODE_100_F	*/
	LINK_DUPLEX_HALF,		/* MII_AUX_STATUS_MODE_1000_H	*/
	LINK_DUPLEX_FULL		/* MII_AUX_STATUS_MODE_1000_F	*/
};

static const char * const bge_copper_link_text[] = {
	"down",				/* MII_AUX_STATUS_MODE_NONE	*/
	"up 10Mbps Half-Duplex",	/* MII_AUX_STATUS_MODE_10_H	*/
	"up 10Mbps Full-Duplex",	/* MII_AUX_STATUS_MODE_10_F	*/
	"up 100Mbps Half-Duplex",	/* MII_AUX_STATUS_MODE_100_H	*/
	"down",				/* MII_AUX_STATUS_MODE_100_4	*/
	"up 100Mbps Full-Duplex",	/* MII_AUX_STATUS_MODE_100_F	*/
	"up 1000Mbps Half-Duplex",	/* MII_AUX_STATUS_MODE_1000_H	*/
	"up 1000Mbps Full-Duplex"	/* MII_AUX_STATUS_MODE_1000_F	*/
};

#if	BGE_DEBUGGING

static void
bge_phydump(bge_t *bgep, uint16_t mii_status, uint16_t aux)
{
	uint16_t regs[32];
	int i;

	ASSERT(mutex_owned(bgep->genlock));

	for (i = 0; i < 32; ++i)
		switch (i) {
		default:
			regs[i] = bge_mii_get16(bgep, i);
			break;

		case MII_STATUS:
			regs[i] = mii_status;
			break;

		case MII_AUX_STATUS:
			regs[i] = aux;
			break;

		case 0x0b: case 0x0c: case 0x0d: case 0x0e:
		case 0x15: case 0x16: case 0x17:
		case 0x1c:
		case 0x1f:
			/* reserved registers -- don't read these */
			regs[i] = 0;
			break;
		}

	for (i = 0; i < 32; i += 8)
		BGE_DEBUG(("bge_phydump: "
				"0x%04x %04x %04x %04x %04x %04x %04x %04x",
			regs[i+0], regs[i+1], regs[i+2], regs[i+3],
			regs[i+4], regs[i+5], regs[i+6], regs[i+7]));
}

#endif	/* BGE_DEBUGGING */

/*
 * Basic low-level function to probe for a PHY
 *
 * Returns TRUE if the PHY responds with valid data, FALSE otherwise
 */
static boolean_t
bge_phy_probe(bge_t *bgep)
{
	uint16_t phy_status;

	BGE_TRACE(("bge_phy_probe($%p)", (void *)bgep));

	ASSERT(mutex_owned(bgep->genlock));

	/*
	 * Read the MII_STATUS register twice, in
	 * order to clear any sticky bits (but they should
	 * have been cleared by the RESET, I think).
	 */
	phy_status = bge_mii_get16(bgep, MII_STATUS);
	phy_status = bge_mii_get16(bgep, MII_STATUS);
	BGE_DEBUG(("bge_phy_probe: status 0x%x", phy_status));

	/*
	 * Now check the value read; it should have at least one bit set
	 * (for the device capabilities) and at least one clear (one of
	 * the error bits). So if we see all 0s or all 1s, there's a
	 * problem.  In particular, bge_mii_get16() returns all 1s if
	 * communications fails ...
	 */
	switch (phy_status) {
	case 0x0000:
	case 0xffff:
		return (B_FALSE);

	default :
		return (B_TRUE);
	}
}

/*
 * Basic low-level function to reset the PHY.
 * Doesn't incorporate any special-case workarounds.
 *
 * Returns TRUE on success, FALSE if the RESET bit doesn't clear
 */
static boolean_t
bge_phy_reset(bge_t *bgep)
{
	uint16_t control;
	uint_t count;

	BGE_TRACE(("bge_phy_reset($%p)", (void *)bgep));

	ASSERT(mutex_owned(bgep->genlock));

	/*
	 * Set the PHY RESET bit, then wait up to 5 ms for it to self-clear
	 */
	bge_mii_put16(bgep, MII_CONTROL, MII_CONTROL_RESET);
	for (count = 0; ++count < 1000; ) {
		drv_usecwait(5);
		control = bge_mii_get16(bgep, MII_CONTROL);
		if (BIC(control, MII_CONTROL_RESET))
			return (B_TRUE);
	}

	BGE_DEBUG(("bge_phy_reset: FAILED, control now 0x%x", control));

	return (B_FALSE);
}

/*
 * Basic low-level function to powerdown the PHY, if supported
 * If powerdown support is compiled out, this function does nothing.
 */
static void
bge_phy_powerdown(bge_t *bgep)
{
	BGE_TRACE(("bge_phy_powerdown"));
#if	BGE_COPPER_IDLEOFF
	bge_mii_put16(bgep, MII_CONTROL, MII_CONTROL_PWRDN);
#endif	/* BGE_COPPER_IDLEOFF */
}

/*
 * The following functions are based on sample code provided by
 * Broadcom (20-June-2003), and implement workarounds said to be
 * required on the early revisions of the BCM5703/4C.
 *
 * The registers and values used are mostly UNDOCUMENTED, and
 * therefore don't have symbolic names ;-(
 *
 * Many of the comments are straight out of the Broadcom code:
 * even where the code has been restructured, the original
 * comments have been preserved in order to explain what these
 * undocumented registers & values are all about ...
 */

static void
bge_phy_macro_wait(bge_t *bgep)
{
	uint_t count;

	for (count = 100; --count; )
		if ((bge_mii_get16(bgep, 0x16) & 0x1000) == 0)
			break;
}

/*
 * PHY test data pattern:
 *
 * For 5703/04, each DFE TAP has 21-bits (low word 15, hi word 6)
 * For 5705,    each DFE TAP has 19-bits (low word 15, hi word 4)
 * For simplicity, we check only 19-bits, so we don't have to
 * distinguish which chip it is.
 * the LO word contains 15 bits, make sure pattern data is < 0x7fff
 * the HI word contains  6 bits, make sure pattern data is < 0x003f
 */
#define	N_CHANNELS	4
#define	N_TAPS		3

static struct {
	uint16_t	lo;
	uint16_t	hi;
} tap_data[N_CHANNELS][N_TAPS] = {
	{
		{ 0x5555, 0x0005 },	/* ch0, TAP 0, LO/HI pattern */
		{ 0x2aaa, 0x000a },	/* ch0, TAP 1, LO/HI pattern */
		{ 0x3456, 0x0003 }	/* ch0, TAP 2, LO/HI pattern */
	},
	{
		{ 0x2aaa, 0x000a },	/* ch1, TAP 0, LO/HI pattern */
		{ 0x3333, 0x0003 },	/* ch1, TAP 1, LO/HI pattern */
		{ 0x789a, 0x0005 }	/* ch1, TAP 2, LO/HI pattern */
	},
	{
		{ 0x5a5a, 0x0005 },	/* ch2, TAP 0, LO/HI pattern */
		{ 0x2a6a, 0x000a },	/* ch2, TAP 1, LO/HI pattern */
		{ 0x1bcd, 0x0003 }	/* ch2, TAP 2, LO/HI pattern */
	},
	{
		{ 0x2a5a, 0x000a },	/* ch3, TAP 0, LO/HI pattern */
		{ 0x33c3, 0x0003 },	/* ch3, TAP 1, LO/HI pattern */
		{ 0x2ef1, 0x0005 }	/* ch3, TAP 2, LO/HI pattern */
	}
};

/*
 * Check whether the PHY has locked up after a RESET.
 *
 * Returns TRUE if it did, FALSE is it's OK ;-)
 */
static boolean_t
bge_phy_locked_up(bge_t *bgep)
{
	uint16_t dataLo;
	uint16_t dataHi;
	uint_t chan;
	uint_t tap;

	/*
	 * Check TAPs for all 4 channels, as soon as we see a lockup
	 * we'll stop checking.
	 */
	for (chan = 0; chan < N_CHANNELS; ++chan) {
		/* Select channel and set TAP index to 0 */
		bge_mii_put16(bgep, 0x17, (chan << 13) | 0x0200);
		/* Freeze filter again just to be safe */
		bge_mii_put16(bgep, 0x16, 0x0002);

		/*
		 * Write fixed pattern to the RAM, 3 TAPs for
		 * each channel, each TAP have 2 WORDs (LO/HI)
		 */
		for (tap = 0; tap < N_TAPS; ++tap) {
			bge_mii_put16(bgep, 0x15, tap_data[chan][tap].lo);
			bge_mii_put16(bgep, 0x15, tap_data[chan][tap].hi);
		}

		/*
		 * Active PHY's Macro operation to write DFE
		 * TAP from RAM, and wait for Macro to complete.
		 */
		bge_mii_put16(bgep, 0x16, 0x0202);
		bge_phy_macro_wait(bgep);

		/*
		 * Done with write phase, now begin read phase.
		 */

		/* Select channel and set TAP index to 0 */
		bge_mii_put16(bgep, 0x17, (chan << 13) | 0x0200);

		/*
		 * Active PHY's Macro operation to load DFE
		 * TAP to RAM, and wait for Macro to complete
		 */
		bge_mii_put16(bgep, 0x16, 0x0082);
		bge_phy_macro_wait(bgep);

		/* Enable "pre-fetch" */
		bge_mii_put16(bgep, 0x16, 0x0802);
		bge_phy_macro_wait(bgep);

		/*
		 * Read back the TAP values.  3 TAPs for each
		 * channel, each TAP have 2 WORDs (LO/HI)
		 */
		for (tap = 0; tap < N_TAPS; ++tap) {
			/*
			 * Read Lo/Hi then wait for 'done' is faster.
			 * For DFE TAP, the HI word contains 6 bits,
			 * LO word contains 15 bits
			 */
			dataLo = bge_mii_get16(bgep, 0x15) & 0x7fff;
			dataHi = bge_mii_get16(bgep, 0x15) & 0x003f;
			bge_phy_macro_wait(bgep);

			/*
			 * Check if what we wrote is what we read back.
			 * If failed, then the PHY is locked up, we need
			 * to do PHY reset again
			 */
			if (dataLo != tap_data[chan][tap].lo)
				return (B_TRUE);	/* wedged!	*/

			if (dataHi != tap_data[chan][tap].hi)
				return (B_TRUE);	/* wedged!	*/
		}
	}

	/*
	 * The PHY isn't locked up ;-)
	 */
	return (B_FALSE);
}

/*
 * Special-case code to reset the PHY on the 5702/5703/5704C/5705/5782.
 * Tries up to 5 times to recover from failure to reset or PHY lockup.
 *
 * Returns TRUE on success, FALSE if there's an unrecoverable problem
 */
static boolean_t
bge_phy_reset_and_check(bge_t *bgep)
{
	boolean_t reset_success;
	boolean_t phy_locked;
	uint16_t extctrl;
	uint_t retries;

	for (retries = 0; retries < 5; ++retries) {
		/* Issue a phy reset, and wait for reset to complete */
		/* Assuming reset is successful first */
		reset_success = bge_phy_reset(bgep);

		/*
		 * Now go check the DFE TAPs to see if locked up, but
		 * first, we need to set up PHY so we can read DFE
		 * TAPs.
		 */

		/*
		 * Disable Transmitter and Interrupt, while we play
		 * with the PHY registers, so the link partner won't
		 * see any strange data and the Driver won't see any
		 * interrupts.
		 */
		extctrl = bge_mii_get16(bgep, 0x10);
		bge_mii_put16(bgep, 0x10, extctrl | 0x3000);

		/* Setup Full-Duplex, 1000 mbps */
		bge_mii_put16(bgep, 0x0, 0x0140);

		/* Set to Master mode */
		bge_mii_put16(bgep, 0x9, 0x1800);

		/* Enable SM_DSP_CLOCK & 6dB */
		bge_mii_put16(bgep, 0x18, 0x0c00);	/* "the ADC fix" */

		/* Work-arounds */
		bge_mii_put16(bgep, 0x17, 0x201f);
		bge_mii_put16(bgep, 0x15, 0x2aaa);

		/* More workarounds */
		bge_mii_put16(bgep, 0x17, 0x000a);
		bge_mii_put16(bgep, 0x15, 0x0323);	/* "the Gamma fix" */

		/* Blocks the PHY control access */
		bge_mii_put16(bgep, 0x17, 0x8005);
		bge_mii_put16(bgep, 0x15, 0x0800);

		/* Test whether PHY locked up ;-( */
		phy_locked = bge_phy_locked_up(bgep);
		if (reset_success && !phy_locked)
			break;

		/*
		 * Some problem here ... log it & retry
		 */
		if (!reset_success)
			BGE_REPORT((bgep, "PHY didn't reset!"));
		if (phy_locked)
			BGE_REPORT((bgep, "PHY locked up!"));
	}

	/* Remove block phy control */
	bge_mii_put16(bgep, 0x17, 0x8005);
	bge_mii_put16(bgep, 0x15, 0x0000);

	/* Unfreeze DFE TAP filter for all channels */
	bge_mii_put16(bgep, 0x17, 0x8200);
	bge_mii_put16(bgep, 0x16, 0x0000);

	/* Restore PHY back to operating state */
	bge_mii_put16(bgep, 0x18, 0x0400);

	/* Enable transmitter and interrupt */
	extctrl = bge_mii_get16(bgep, 0x10);
	bge_mii_put16(bgep, 0x10, extctrl & ~0x3000);

	return (reset_success && !phy_locked);
}

static void
bge_phy_tweak_gmii(bge_t *bgep)
{
	/* Tweak GMII timing */
	bge_mii_put16(bgep, 0x1c, 0x8d68);
	bge_mii_put16(bgep, 0x1c, 0x8d68);
}

/*
 * End of Broadcom-derived workaround code				*
 */

static void
bge_restart_copper(bge_t *bgep, boolean_t powerdown)
{
	uint16_t phy_status;
	boolean_t reset_ok;

	BGE_TRACE(("bge_restart_copper($%p, %d)", (void *)bgep, powerdown));

	ASSERT(mutex_owned(bgep->genlock));

	switch (MHCR_CHIP_ASIC_REV(bgep->chipid.asic_rev)) {
	default:
		/*
		 * Shouldn't happen; it means we don't recognise this chip.
		 * It's probably a new one, so we'll try our best anyway ...
		 */
	case MHCR_CHIP_ASIC_REV_5703:
	case MHCR_CHIP_ASIC_REV_5704:
	case MHCR_CHIP_ASIC_REV_5705:
	case MHCR_CHIP_ASIC_REV_5721_5751:
	case MHCR_CHIP_ASIC_REV_5714:
	case MHCR_CHIP_ASIC_REV_5715:
		reset_ok = bge_phy_reset_and_check(bgep);
		break;

	case MHCR_CHIP_ASIC_REV_5700:
	case MHCR_CHIP_ASIC_REV_5701:
		/*
		 * Just a plain reset; the "check" code breaks these chips
		 */
		reset_ok = bge_phy_reset(bgep);
		break;
	}
	if (!reset_ok)
		bge_problem(bgep, "PHY failed to reset correctly");

	/*
	 * Step 5: disable WOL (not required after RESET)
	 *
	 * Step 6: refer to errata
	 */
	switch (bgep->chipid.asic_rev) {
	default:
		break;

	case MHCR_CHIP_REV_5704_A0:
		bge_phy_tweak_gmii(bgep);
		break;
	}

	/*
	 * Step 7: read the MII_INTR_STATUS register twice,
	 * in order to clear any sticky bits (but they should
	 * have been cleared by the RESET, I think), and we're
	 * not using PHY interrupts anyway.
	 *
	 * Step 8: enable the PHY to interrupt on link status
	 * change (not required)
	 *
	 * Step 9: configure PHY LED Mode - not applicable?
	 *
	 * Step 10: read the MII_STATUS register twice, in
	 * order to clear any sticky bits (but they should
	 * have been cleared by the RESET, I think).
	 */
	phy_status = bge_mii_get16(bgep, MII_STATUS);
	phy_status = bge_mii_get16(bgep, MII_STATUS);
	BGE_DEBUG(("bge_restart_copper: status 0x%x", phy_status));

	/*
	 * Finally, shut down the PHY, if required
	 */
	if (powerdown)
		bge_phy_powerdown(bgep);
}

/*
 * Synchronise the (copper) PHY's speed/duplex/autonegotiation capabilities
 * and advertisements with the required settings as specified by the various
 * param_* variables that can be poked via the NDD interface.
 *
 * We always reset the PHY and reprogram *all* the relevant registers,
 * not just those changed.  This should cause the link to go down, and then
 * back up again once the link is stable and autonegotiation (if enabled)
 * is complete.  We should get a link state change interrupt somewhere along
 * the way ...
 *
 * NOTE: <genlock> must already be held by the caller
 */
static void
bge_update_copper(bge_t *bgep)
{
	boolean_t adv_autoneg;
	boolean_t adv_pause;
	boolean_t adv_asym_pause;
	boolean_t adv_1000fdx;
	boolean_t adv_1000hdx;
	boolean_t adv_100fdx;
	boolean_t adv_100hdx;
	boolean_t adv_10fdx;
	boolean_t adv_10hdx;

	uint16_t control;
	uint16_t gigctrl;
	uint16_t auxctrl;
	uint16_t anar;

	BGE_TRACE(("bge_update_copper($%p)", (void *)bgep));

	ASSERT(mutex_owned(bgep->genlock));

	BGE_DEBUG(("bge_update_copper: autoneg %d "
			"pause %d asym_pause %d "
			"1000fdx %d 1000hdx %d "
			"100fdx %d 100hdx %d "
			"10fdx %d 10hdx %d ",
		bgep->param_adv_autoneg,
		bgep->param_adv_pause, bgep->param_adv_asym_pause,
		bgep->param_adv_1000fdx, bgep->param_adv_1000hdx,
		bgep->param_adv_100fdx, bgep->param_adv_100hdx,
		bgep->param_adv_10fdx, bgep->param_adv_10hdx));

	control = gigctrl = auxctrl = anar = 0;

	/*
	 * PHY settings are normally based on the param_* variables,
	 * but if any loopback mode is in effect, that takes precedence.
	 *
	 * BGE supports MAC-internal loopback, PHY-internal loopback,
	 * and External loopback at a variety of speeds (with a special
	 * cable).  In all cases, autoneg is turned OFF, full-duplex
	 * is turned ON, and the speed/mastership is forced.
	 */
	switch (bgep->param_loop_mode) {
	case BGE_LOOP_NONE:
	default:
		adv_autoneg = bgep->param_adv_autoneg;
		adv_pause = bgep->param_adv_pause;
		adv_asym_pause = bgep->param_adv_asym_pause;
		adv_1000fdx = bgep->param_adv_1000fdx;
		adv_1000hdx = bgep->param_adv_1000hdx;
		adv_100fdx = bgep->param_adv_100fdx;
		adv_100hdx = bgep->param_adv_100hdx;
		adv_10fdx = bgep->param_adv_10fdx;
		adv_10hdx = bgep->param_adv_10hdx;
		break;

	case BGE_LOOP_EXTERNAL_1000:
	case BGE_LOOP_EXTERNAL_100:
	case BGE_LOOP_EXTERNAL_10:
	case BGE_LOOP_INTERNAL_PHY:
	case BGE_LOOP_INTERNAL_MAC:
		adv_autoneg = adv_pause = adv_asym_pause = B_FALSE;
		adv_1000fdx = adv_100fdx = adv_10fdx = B_FALSE;
		adv_1000hdx = adv_100hdx = adv_10hdx = B_FALSE;
		bgep->param_link_duplex = LINK_DUPLEX_FULL;

		switch (bgep->param_loop_mode) {
		case BGE_LOOP_EXTERNAL_1000:
			bgep->param_link_speed = 1000;
			adv_1000fdx = B_TRUE;
			auxctrl = MII_AUX_CTRL_NORM_EXT_LOOPBACK;
			gigctrl |= MII_1000BT_CTL_MASTER_CFG;
			gigctrl |= MII_1000BT_CTL_MASTER_SEL;
			break;

		case BGE_LOOP_EXTERNAL_100:
			bgep->param_link_speed = 100;
			adv_100fdx = B_TRUE;
			auxctrl = MII_AUX_CTRL_NORM_EXT_LOOPBACK;
			break;

		case BGE_LOOP_EXTERNAL_10:
			bgep->param_link_speed = 10;
			adv_10fdx = B_TRUE;
			auxctrl = MII_AUX_CTRL_NORM_EXT_LOOPBACK;
			break;

		case BGE_LOOP_INTERNAL_PHY:
			bgep->param_link_speed = 1000;
			adv_1000fdx = B_TRUE;
			control = MII_CONTROL_LOOPBACK;
			break;

		case BGE_LOOP_INTERNAL_MAC:
			bgep->param_link_speed = 1000;
			adv_1000fdx = B_TRUE;
			break;
		}
	}

	BGE_DEBUG(("bge_update_copper: autoneg %d "
			"pause %d asym_pause %d "
			"1000fdx %d 1000hdx %d "
			"100fdx %d 100hdx %d "
			"10fdx %d 10hdx %d ",
		adv_autoneg,
		adv_pause, adv_asym_pause,
		adv_1000fdx, adv_1000hdx,
		adv_100fdx, adv_100hdx,
		adv_10fdx, adv_10hdx));

	/*
	 * We should have at least one technology capability set;
	 * if not, we select a default of 1000Mb/s full-duplex
	 */
	if (!adv_1000fdx && !adv_100fdx && !adv_10fdx &&
	    !adv_1000hdx && !adv_100hdx && !adv_10hdx)
		adv_1000fdx = B_TRUE;

	/*
	 * Now transform the adv_* variables into the proper settings
	 * of the PHY registers ...
	 *
	 * If autonegotiation is (now) enabled, we want to trigger
	 * a new autonegotiation cycle once the PHY has been
	 * programmed with the capabilities to be advertised.
	 */
	if (adv_autoneg)
		control |= MII_CONTROL_ANE|MII_CONTROL_RSAN;

	if (adv_1000fdx)
		control |= MII_CONTROL_1000MB|MII_CONTROL_FDUPLEX;
	else if (adv_1000hdx)
		control |= MII_CONTROL_1000MB;
	else if (adv_100fdx)
		control |= MII_CONTROL_100MB|MII_CONTROL_FDUPLEX;
	else if (adv_100hdx)
		control |= MII_CONTROL_100MB;
	else if (adv_10fdx)
		control |= MII_CONTROL_FDUPLEX;
	else if (adv_10hdx)
		control |= 0;
	else
		{ _NOTE(EMPTY); }	/* Can't get here anyway ...	*/

	if (adv_1000fdx)
		gigctrl |= MII_1000BT_CTL_ADV_FDX;
	if (adv_1000hdx)
		gigctrl |= MII_1000BT_CTL_ADV_HDX;

	if (adv_100fdx)
		anar |= MII_ABILITY_100BASE_TX_FD;
	if (adv_100hdx)
		anar |= MII_ABILITY_100BASE_TX;
	if (adv_10fdx)
		anar |= MII_ABILITY_10BASE_T_FD;
	if (adv_10hdx)
		anar |= MII_ABILITY_10BASE_T;

	if (adv_pause)
		anar |= MII_ABILITY_PAUSE;
	if (adv_asym_pause)
		anar |= MII_ABILITY_ASYM_PAUSE;

	/*
	 * Munge in any other fixed bits we require ...
	 */
	anar |= MII_AN_SELECTOR_8023;
	auxctrl |= MII_AUX_CTRL_NORM_TX_MODE;
	auxctrl |= MII_AUX_CTRL_NORMAL;

	/*
	 * Restart the PHY and write the new values.  Note the
	 * time, so that we can say whether subsequent link state
	 * changes can be attributed to our reprogramming the PHY
	 */
	bgep->phys_write_time = gethrtime();
	(*bgep->physops->phys_restart)(bgep, B_FALSE);
	bge_mii_put16(bgep, MII_AN_ADVERT, anar);
	bge_mii_put16(bgep, MII_CONTROL, control);
	bge_mii_put16(bgep, MII_AUX_CONTROL, auxctrl);
	bge_mii_put16(bgep, MII_1000BASE_T_CONTROL, gigctrl);

	BGE_DEBUG(("bge_update_copper: anar <- 0x%x", anar));
	BGE_DEBUG(("bge_update_copper: control <- 0x%x", control));
	BGE_DEBUG(("bge_update_copper: auxctrl <- 0x%x", auxctrl));
	BGE_DEBUG(("bge_update_copper: gigctrl <- 0x%x", gigctrl));

#if	BGE_COPPER_WIRESPEED
	/*
	 * Enable the 'wire-speed' feature, if the chip supports it
	 * and we haven't got (any) loopback mode selected.
	 */
	switch (bgep->chipid.device) {
	case DEVICE_ID_5700:
	case DEVICE_ID_5700x:
	case DEVICE_ID_5705C:
	case DEVICE_ID_5782:
		/*
		 * These chips are known or assumed not to support it
		 */
		break;

	default:
		/*
		 * All other Broadcom chips are expected to support it.
		 */
		if (bgep->param_loop_mode == BGE_LOOP_NONE)
			bge_mii_put16(bgep, MII_AUX_CONTROL,
					MII_AUX_CTRL_MISC_WRITE_ENABLE |
					MII_AUX_CTRL_MISC_WIRE_SPEED |
					MII_AUX_CTRL_MISC);
		break;
	}
#endif	/* BGE_COPPER_WIRESPEED */
}

static boolean_t
bge_check_copper(bge_t *bgep, boolean_t recheck)
{
	uint32_t emac_status;
	uint16_t mii_status;
	uint16_t aux;
	uint_t mode;
	boolean_t linkup;

	/*
	 * Step 10: read the status from the PHY (which is self-clearing
	 * on read!); also read & clear the main (Ethernet) MAC status
	 * (the relevant bits of this are write-one-to-clear).
	 */
	mii_status = bge_mii_get16(bgep, MII_STATUS);
	emac_status = bge_reg_get32(bgep, ETHERNET_MAC_STATUS_REG);
	bge_reg_put32(bgep, ETHERNET_MAC_STATUS_REG, emac_status);

	BGE_DEBUG(("bge_check_copper: link %d/%s, MII status 0x%x "
			"(was 0x%x), Ethernet MAC status 0x%x",
		bgep->link_state, UPORDOWN(bgep->param_link_up), mii_status,
		bgep->phy_gen_status, emac_status));

	/*
	 * If the PHY status hasn't changed since last we looked, and
	 * we not forcing a recheck (i.e. the link state was already
	 * known), there's nothing to do.
	 */
	if (mii_status == bgep->phy_gen_status && !recheck)
		return (B_FALSE);

	do {
		/*
		 * If the PHY status changed, record the time
		 */
		if (mii_status != bgep->phy_gen_status)
			bgep->phys_event_time = gethrtime();

		/*
		 * Step 11: read AUX STATUS register to find speed/duplex
		 */
		aux = bge_mii_get16(bgep, MII_AUX_STATUS);
		BGE_CDB(bge_phydump, (bgep, mii_status, aux));

		/*
		 * We will only consider the link UP if all the readings
		 * are consistent and give meaningful results ...
		 */
		mode = aux & MII_AUX_STATUS_MODE_MASK;
		mode >>= MII_AUX_STATUS_MODE_SHIFT;
		linkup = bge_copper_link_speed[mode] > 0;
		linkup &= bge_copper_link_duplex[mode] != LINK_DUPLEX_UNKNOWN;
		linkup &= BIS(aux, MII_AUX_STATUS_LINKUP);
		linkup &= BIS(mii_status, MII_STATUS_LINKUP);

		BGE_DEBUG(("bge_check_copper: MII status 0x%x aux 0x%x "
				"=> mode %d (%s)",
			mii_status, aux,
			mode, UPORDOWN(linkup)));

		/*
		 * Record current register values, then reread status
		 * register & loop until it stabilises ...
		 */
		bgep->phy_aux_status = aux;
		bgep->phy_gen_status = mii_status;
		mii_status = bge_mii_get16(bgep, MII_STATUS);
	} while (mii_status != bgep->phy_gen_status);

	/*
	 * Assume very little ...
	 */
	bgep->param_lp_autoneg = B_FALSE;
	bgep->param_lp_1000fdx = B_FALSE;
	bgep->param_lp_1000hdx = B_FALSE;
	bgep->param_lp_100fdx = B_FALSE;
	bgep->param_lp_100hdx = B_FALSE;
	bgep->param_lp_10fdx = B_FALSE;
	bgep->param_lp_10hdx = B_FALSE;
	bgep->param_lp_pause = B_FALSE;
	bgep->param_lp_asym_pause = B_FALSE;
	bgep->param_link_autoneg = B_FALSE;
	bgep->param_link_tx_pause = B_FALSE;
	if (bgep->param_adv_autoneg)
		bgep->param_link_rx_pause = B_FALSE;
	else
		bgep->param_link_rx_pause = bgep->param_adv_pause;

	/*
	 * Discover all the link partner's abilities.
	 * These are scattered through various registters ...
	 */
	if (BIS(aux, MII_AUX_STATUS_LP_ANEG_ABLE)) {
		bgep->param_lp_autoneg = B_TRUE;
		bgep->param_link_autoneg = B_TRUE;
		bgep->param_link_tx_pause = BIS(aux, MII_AUX_STATUS_TX_PAUSE);
		bgep->param_link_rx_pause = BIS(aux, MII_AUX_STATUS_RX_PAUSE);

		aux = bge_mii_get16(bgep, MII_1000BASE_T_STATUS);
		bgep->param_lp_1000fdx = BIS(aux, MII_1000BT_STAT_LP_FDX_CAP);
		bgep->param_lp_1000hdx = BIS(aux, MII_1000BT_STAT_LP_HDX_CAP);

		aux = bge_mii_get16(bgep, MII_AN_LPABLE);
		bgep->param_lp_100fdx = BIS(aux, MII_ABILITY_100BASE_TX_FD);
		bgep->param_lp_100hdx = BIS(aux, MII_ABILITY_100BASE_TX);
		bgep->param_lp_10fdx = BIS(aux, MII_ABILITY_10BASE_T_FD);
		bgep->param_lp_10hdx = BIS(aux, MII_ABILITY_10BASE_T);
		bgep->param_lp_pause = BIS(aux, MII_ABILITY_PAUSE);
		bgep->param_lp_asym_pause = BIS(aux, MII_ABILITY_ASYM_PAUSE);
	}

	/*
	 * Step 12: update ndd-visible state parameters, BUT!
	 * we don't transfer the new state to <link_state> just yet;
	 * instead we mark the <link_state> as UNKNOWN, and our caller
	 * will resolve it once the status has stopped changing and
	 * been stable for several seconds.
	 */
	BGE_DEBUG(("bge_check_copper: link was %s speed %d duplex %d",
		UPORDOWN(bgep->param_link_up),
		bgep->param_link_speed,
		bgep->param_link_duplex));

	if (!linkup)
		mode = MII_AUX_STATUS_MODE_NONE;
	bgep->param_link_up = linkup;
	bgep->param_link_speed = bge_copper_link_speed[mode];
	bgep->param_link_duplex = bge_copper_link_duplex[mode];
	bgep->link_mode_msg = bge_copper_link_text[mode];
	bgep->link_state = LINK_STATE_UNKNOWN;

	BGE_DEBUG(("bge_check_copper: link now %s speed %d duplex %d",
		UPORDOWN(bgep->param_link_up),
		bgep->param_link_speed,
		bgep->param_link_duplex));

	return (B_TRUE);
}

static const phys_ops_t copper_ops = {
	bge_restart_copper,
	bge_update_copper,
	bge_check_copper
};


/*
 * ========== SerDes support ==========
 */

#undef	BGE_DBG
#define	BGE_DBG		BGE_DBG_SERDES	/* debug flag for this code	*/

/*
 * Reinitialise the SerDes interface.  Note that it normally powers
 * up in the disabled state, so we need to explicitly activate it.
 */
static void
bge_restart_serdes(bge_t *bgep, boolean_t powerdown)
{
	uint32_t macmode;

	BGE_TRACE(("bge_restart_serdes($%p, %d)", (void *)bgep, powerdown));

	ASSERT(mutex_owned(bgep->genlock));

	/*
	 * Ensure that the main Ethernet MAC mode register is programmed
	 * appropriately for the SerDes interface ...
	 */
	macmode = bge_reg_get32(bgep, ETHERNET_MAC_MODE_REG);
	macmode &= ~ETHERNET_MODE_LINK_POLARITY;
	macmode &= ~ETHERNET_MODE_PORTMODE_MASK;
	macmode |= ETHERNET_MODE_PORTMODE_TBI;
	bge_reg_put32(bgep, ETHERNET_MAC_MODE_REG, macmode);

	/*
	 * Ensure that loopback is OFF and comma detection is enabled.  Then
	 * disable the SerDes output (the first time through, it may/will
	 * already be disabled).  If we're shutting down, leave it disabled.
	 */
	bge_reg_clr32(bgep, SERDES_CONTROL_REG, SERDES_CONTROL_TBI_LOOPBACK);
	bge_reg_set32(bgep, SERDES_CONTROL_REG, SERDES_CONTROL_COMMA_DETECT);
	bge_reg_set32(bgep, SERDES_CONTROL_REG, SERDES_CONTROL_TX_DISABLE);
	if (powerdown)
		return;

	/*
	 * Otherwise, pause, (re-)enable the SerDes output, and send
	 * all-zero config words in order to force autoneg restart.
	 * Invalidate the saved "link partners received configs", as
	 * we're starting over ...
	 */
	drv_usecwait(10000);
	bge_reg_clr32(bgep, SERDES_CONTROL_REG, SERDES_CONTROL_TX_DISABLE);
	bge_reg_put32(bgep, TX_1000BASEX_AUTONEG_REG, 0);
	bge_reg_set32(bgep, ETHERNET_MAC_MODE_REG, ETHERNET_MODE_SEND_CFGS);
	drv_usecwait(10);
	bge_reg_clr32(bgep, ETHERNET_MAC_MODE_REG, ETHERNET_MODE_SEND_CFGS);
	bgep->serdes_lpadv = AUTONEG_CODE_FAULT_ANEG_ERR;
	bgep->serdes_status = ~0U;
}

/*
 * Synchronise the SerDes speed/duplex/autonegotiation capabilities and
 * advertisements with the required settings as specified by the various
 * param_* variables that can be poked via the NDD interface.
 *
 * We always reinitalise the SerDes; this should cause the link to go down,
 * and then back up again once the link is stable and autonegotiation
 * (if enabled) is complete.  We should get a link state change interrupt
 * somewhere along the way ...
 *
 * NOTE: SerDes only supports 1000FDX/HDX (with or without pause) so the
 * param_* variables relating to lower speeds are ignored.
 *
 * NOTE: <genlock> must already be held by the caller
 */
static void
bge_update_serdes(bge_t *bgep)
{
	boolean_t adv_autoneg;
	boolean_t adv_pause;
	boolean_t adv_asym_pause;
	boolean_t adv_1000fdx;
	boolean_t adv_1000hdx;

	uint32_t serdes;
	uint32_t advert;

	BGE_TRACE(("bge_update_serdes($%p)", (void *)bgep));

	ASSERT(mutex_owned(bgep->genlock));

	BGE_DEBUG(("bge_update_serdes: autoneg %d "
			"pause %d asym_pause %d "
			"1000fdx %d 1000hdx %d "
			"100fdx %d 100hdx %d "
			"10fdx %d 10hdx %d ",
		bgep->param_adv_autoneg,
		bgep->param_adv_pause, bgep->param_adv_asym_pause,
		bgep->param_adv_1000fdx, bgep->param_adv_1000hdx,
		bgep->param_adv_100fdx, bgep->param_adv_100hdx,
		bgep->param_adv_10fdx, bgep->param_adv_10hdx));

	serdes = advert = 0;

	/*
	 * SerDes settings are normally based on the param_* variables,
	 * but if any loopback mode is in effect, that takes precedence.
	 *
	 * BGE supports MAC-internal loopback, PHY-internal loopback,
	 * and External loopback at a variety of speeds (with a special
	 * cable).  In all cases, autoneg is turned OFF, full-duplex
	 * is turned ON, and the speed/mastership is forced.
	 *
	 * Note: for the SerDes interface, "PHY" internal loopback is
	 * interpreted as SerDes internal loopback, and all external
	 * loopback modes are treated equivalently, as 1Gb/external.
	 */
	switch (bgep->param_loop_mode) {
	case BGE_LOOP_NONE:
	default:
		adv_autoneg = bgep->param_adv_autoneg;
		adv_pause = bgep->param_adv_pause;
		adv_asym_pause = bgep->param_adv_asym_pause;
		adv_1000fdx = bgep->param_adv_1000fdx;
		adv_1000hdx = bgep->param_adv_1000hdx;
		break;

	case BGE_LOOP_INTERNAL_PHY:
		serdes |= SERDES_CONTROL_TBI_LOOPBACK;
		/* FALLTHRU */
	case BGE_LOOP_INTERNAL_MAC:
	case BGE_LOOP_EXTERNAL_1000:
	case BGE_LOOP_EXTERNAL_100:
	case BGE_LOOP_EXTERNAL_10:
		adv_autoneg = adv_pause = adv_asym_pause = B_FALSE;
		adv_1000fdx = B_TRUE;
		adv_1000hdx = B_FALSE;
		break;
	}

	BGE_DEBUG(("bge_update_serdes: autoneg %d "
			"pause %d asym_pause %d "
			"1000fdx %d 1000hdx %d ",
		adv_autoneg,
		adv_pause, adv_asym_pause,
		adv_1000fdx, adv_1000hdx));

	/*
	 * We should have at least one gigabit technology capability
	 * set; if not, we select a default of 1000Mb/s full-duplex
	 */
	if (!adv_1000fdx && !adv_1000hdx)
		adv_1000fdx = B_TRUE;

	/*
	 * Now transform the adv_* variables into the proper settings
	 * of the SerDes registers ...
	 *
	 * If autonegotiation is (now) not enabled, pretend it's been
	 * done and failed ...
	 */
	if (!adv_autoneg)
		advert |= AUTONEG_CODE_FAULT_ANEG_ERR;

	if (adv_1000fdx) {
		advert |= AUTONEG_CODE_FULL_DUPLEX;
		bgep->param_adv_1000fdx = adv_1000fdx;
		bgep->param_link_duplex = LINK_DUPLEX_FULL;
		bgep->param_link_speed = 1000;
	}
	if (adv_1000hdx) {
		advert |= AUTONEG_CODE_HALF_DUPLEX;
		bgep->param_adv_1000hdx = adv_1000hdx;
		bgep->param_link_duplex = LINK_DUPLEX_HALF;
		bgep->param_link_speed = 1000;
	}

	if (adv_pause)
		advert |= AUTONEG_CODE_PAUSE;
	if (adv_asym_pause)
		advert |= AUTONEG_CODE_ASYM_PAUSE;

	/*
	 * Restart the SerDes and write the new values.  Note the
	 * time, so that we can say whether subsequent link state
	 * changes can be attributed to our reprogramming the SerDes
	 */
	bgep->serdes_advert = advert;
	bgep->phys_write_time = gethrtime();
	bge_restart_serdes(bgep, B_FALSE);
	bge_reg_set32(bgep, SERDES_CONTROL_REG, serdes);

	BGE_DEBUG(("bge_update_serdes: serdes |= 0x%x, advert 0x%x",
		serdes, advert));
}

/*
 * Bare-minimum autoneg protocol
 *
 * This code is only called when the link is up and we're receiving config
 * words, which implies that the link partner wants to autonegotiate
 * (otherwise, we wouldn't see configs and wouldn't reach this code).
 */
static void
bge_autoneg_serdes(bge_t *bgep)
{
	boolean_t ack;

	bgep->serdes_lpadv = bge_reg_get32(bgep, RX_1000BASEX_AUTONEG_REG);
	ack = BIS(bgep->serdes_lpadv, AUTONEG_CODE_ACKNOWLEDGE);

	if (!ack) {
		/*
		 * Phase 1: after SerDes reset, we send a few zero configs
		 * but then stop.  Here the partner is sending configs, but
		 * not ACKing ours; we assume that's 'cos we're not sending
		 * any.  So here we send ours, with ACK already set.
		 */
		bge_reg_put32(bgep, TX_1000BASEX_AUTONEG_REG,
			bgep->serdes_advert | AUTONEG_CODE_ACKNOWLEDGE);
		bge_reg_set32(bgep, ETHERNET_MAC_MODE_REG,
			ETHERNET_MODE_SEND_CFGS);
	} else {
		/*
		 * Phase 2: partner has ACKed our configs, so now we can
		 * stop sending; once our partner also stops sending, we
		 * can resolve the Tx/Rx configs.
		 */
		bge_reg_clr32(bgep, ETHERNET_MAC_MODE_REG,
			ETHERNET_MODE_SEND_CFGS);
	}

	BGE_DEBUG(("bge_autoneg_serdes: Rx 0x%x %s Tx 0x%x",
		bgep->serdes_lpadv,
		ack ? "stop" : "send",
		bgep->serdes_advert));
}

static boolean_t
bge_check_serdes(bge_t *bgep, boolean_t recheck)
{
	uint32_t emac_status;
	uint32_t lpadv;
	boolean_t linkup;

	for (;;) {
		/*
		 * Step 10: read & clear the main (Ethernet) MAC status
		 * (the relevant bits of this are write-one-to-clear).
		 */
		emac_status = bge_reg_get32(bgep, ETHERNET_MAC_STATUS_REG);
		bge_reg_put32(bgep, ETHERNET_MAC_STATUS_REG, emac_status);

		BGE_DEBUG(("bge_check_serdes: link %d/%s, "
				"MAC status 0x%x (was 0x%x)",
			bgep->link_state, UPORDOWN(bgep->param_link_up),
			emac_status, bgep->serdes_status));

		/*
		 * We will only consider the link UP if all the readings
		 * are consistent and give meaningful results ...
		 */
		bgep->serdes_status = emac_status;
		linkup = BIS(emac_status, ETHERNET_STATUS_SIGNAL_DETECT);
		linkup &= BIS(emac_status, ETHERNET_STATUS_PCS_SYNCHED);

		/*
		 * Now some fiddling with the interpretation:
		 *	if there's been an error at the PCS level, treat
		 *	it as a link change (the h/w doesn't do this)
		 *
		 *	if there's been a change, but it's only a PCS sync
		 *	change (not a config change), AND the link already
		 *	was & is still UP, then ignore the change
		 */
		if (BIS(emac_status, ETHERNET_STATUS_PCS_ERROR))
			emac_status |= ETHERNET_STATUS_LINK_CHANGED;
		else if (BIC(emac_status, ETHERNET_STATUS_CFG_CHANGED))
			if (bgep->param_link_up && linkup)
				emac_status &= ~ETHERNET_STATUS_LINK_CHANGED;

		BGE_DEBUG(("bge_check_serdes: status 0x%x => 0x%x %s",
			bgep->serdes_status, emac_status, UPORDOWN(linkup)));

		/*
		 * If we're receiving configs, run the autoneg protocol
		 */
		if (linkup && BIS(emac_status, ETHERNET_STATUS_RECEIVING_CFG))
			bge_autoneg_serdes(bgep);

		/*
		 * If the SerDes status hasn't changed, we're done ...
		 */
		if (BIC(emac_status, ETHERNET_STATUS_LINK_CHANGED))
			break;

		/*
		 * Record when the SerDes status changed, then go
		 * round again until we no longer see a change ...
		 */
		bgep->phys_event_time = gethrtime();
		recheck = B_TRUE;
	}

	/*
	 * If we're not forcing a recheck (i.e. the link state was already
	 * known), and we didn't see the hardware flag a change, there's
	 * no more to do (and we tell the caller nothing happened).
	 */
	if (!recheck)
		return (B_FALSE);

	/*
	 * Don't resolve autoneg until we're no longer receiving configs
	 */
	if (linkup && BIS(emac_status, ETHERNET_STATUS_RECEIVING_CFG))
		return (B_FALSE);

	/*
	 * Assume very little ...
	 */
	bgep->param_lp_autoneg = B_FALSE;
	bgep->param_lp_1000fdx = B_FALSE;
	bgep->param_lp_1000hdx = B_FALSE;
	bgep->param_lp_100fdx = B_FALSE;
	bgep->param_lp_100hdx = B_FALSE;
	bgep->param_lp_10fdx = B_FALSE;
	bgep->param_lp_10hdx = B_FALSE;
	bgep->param_lp_pause = B_FALSE;
	bgep->param_lp_asym_pause = B_FALSE;
	bgep->param_link_autoneg = B_FALSE;
	bgep->param_link_tx_pause = B_FALSE;
	if (bgep->param_adv_autoneg)
		bgep->param_link_rx_pause = B_FALSE;
	else
		bgep->param_link_rx_pause = bgep->param_adv_pause;

	/*
	 * Discover all the link partner's abilities.
	 */
	lpadv = bgep->serdes_lpadv;
	if (lpadv != 0 && BIC(lpadv, AUTONEG_CODE_FAULT_MASK)) {
		/*
		 * No fault, so derive partner's capabilities
		 */
		bgep->param_lp_autoneg = B_TRUE;
		bgep->param_lp_1000fdx = BIS(lpadv, AUTONEG_CODE_FULL_DUPLEX);
		bgep->param_lp_1000hdx = BIS(lpadv, AUTONEG_CODE_HALF_DUPLEX);
		bgep->param_lp_pause = BIS(lpadv, AUTONEG_CODE_PAUSE);
		bgep->param_lp_asym_pause = BIS(lpadv, AUTONEG_CODE_ASYM_PAUSE);

		/*
		 * Pause direction resolution
		 */
		bgep->param_link_autoneg = B_TRUE;
		if (bgep->param_adv_pause &&
		    bgep->param_lp_pause) {
			bgep->param_link_tx_pause = B_TRUE;
			bgep->param_link_rx_pause = B_TRUE;
		}
		if (bgep->param_adv_asym_pause &&
		    bgep->param_lp_asym_pause) {
			if (bgep->param_adv_pause)
				bgep->param_link_rx_pause = B_TRUE;
			if (bgep->param_lp_pause)
				bgep->param_link_tx_pause = B_TRUE;
		}
	}

	/*
	 * Step 12: update ndd-visible state parameters, BUT!
	 * we don't transfer the new state to <link_state> just yet;
	 * instead we mark the <link_state> as UNKNOWN, and our caller
	 * will resolve it once the status has stopped changing and
	 * been stable for several seconds.
	 */
	BGE_DEBUG(("bge_check_serdes: link was %s speed %d duplex %d",
		UPORDOWN(bgep->param_link_up),
		bgep->param_link_speed,
		bgep->param_link_duplex));

	if (linkup) {
		bgep->param_link_up = B_TRUE;
		bgep->param_link_speed = 1000;
		if (bgep->param_adv_1000fdx)
			bgep->param_link_duplex = LINK_DUPLEX_FULL;
		else
			bgep->param_link_duplex = LINK_DUPLEX_HALF;
		if (bgep->param_lp_autoneg && !bgep->param_lp_1000fdx)
			bgep->param_link_duplex = LINK_DUPLEX_HALF;
	} else {
		bgep->param_link_up = B_FALSE;
		bgep->param_link_speed = 0;
		bgep->param_link_duplex = LINK_DUPLEX_UNKNOWN;
	}
	switch (bgep->param_link_duplex) {
	default:
	case LINK_DUPLEX_UNKNOWN:
		bgep->link_mode_msg = "down";
		break;

	case LINK_DUPLEX_HALF:
		bgep->link_mode_msg = "up 1000Mbps Half-Duplex";
		break;

	case LINK_DUPLEX_FULL:
		bgep->link_mode_msg = "up 1000Mbps Full-Duplex";
		break;
	}
	bgep->link_state = LINK_STATE_UNKNOWN;

	BGE_DEBUG(("bge_check_serdes: link now %s speed %d duplex %d",
		UPORDOWN(bgep->param_link_up),
		bgep->param_link_speed,
		bgep->param_link_duplex));

	return (B_TRUE);
}

static const phys_ops_t serdes_ops = {
	bge_restart_serdes,
	bge_update_serdes,
	bge_check_serdes
};

/*
 * ========== Exported physical layer control routines ==========
 */

#undef	BGE_DBG
#define	BGE_DBG		BGE_DBG_PHYS	/* debug flag for this code	*/

/*
 * Here we have to determine which media we're using (copper or serdes).
 * Once that's done, we can initialise the physical layer appropriately.
 */
void
bge_phys_init(bge_t *bgep)
{
	BGE_TRACE(("bge_phys_init($%p)", (void *)bgep));

	mutex_enter(bgep->genlock);

	/*
	 * Probe for the (internal) PHY.  If it's not there, we'll assume
	 * that this is a 5703/4S, with a SerDes interface rather than
	 * a PHY. BCM5714S/BCM5715S are not supported.It are based on
	 * BCM800x PHY.
	 */
	bgep->phy_mii_addr = 1;
	if (bge_phy_probe(bgep)) {
		bgep->chipid.flags &= ~CHIP_FLAG_SERDES;
		bgep->phys_delta_time = BGE_PHY_STABLE_TIME;
		bgep->physops = &copper_ops;
	} else {
		bgep->chipid.flags |= CHIP_FLAG_SERDES;
		bgep->phys_delta_time = BGE_SERDES_STABLE_TIME;
		bgep->physops = &serdes_ops;
	}

	(*bgep->physops->phys_restart)(bgep, B_FALSE);
	mutex_exit(bgep->genlock);
}

/*
 * Reset the physical layer
 */
void
bge_phys_reset(bge_t *bgep)
{
	BGE_TRACE(("bge_phys_reset($%p)", (void *)bgep));

	mutex_enter(bgep->genlock);
	(*bgep->physops->phys_restart)(bgep, B_FALSE);
	mutex_exit(bgep->genlock);
}

/*
 * Reset and power off the physical layer.
 *
 * Another RESET should get it back to working, but it may take a few
 * seconds it may take a few moments to return to normal operation ...
 */
void
bge_phys_idle(bge_t *bgep)
{
	BGE_TRACE(("bge_phys_idle($%p)", (void *)bgep));

	ASSERT(mutex_owned(bgep->genlock));
	(*bgep->physops->phys_restart)(bgep, B_TRUE);
}

/*
 * Synchronise the PHYSICAL layer's speed/duplex/autonegotiation capabilities
 * and advertisements with the required settings as specified by the various
 * param_* variables that can be poked via the NDD interface.
 *
 * We always reset the PHYSICAL layer and reprogram *all* relevant registers.
 * This is expected to cause the link to go down, and then back up again once
 * the link is stable and autonegotiation (if enabled) is complete.  We should
 * get a link state change interrupt somewhere along the way ...
 *
 * NOTE: <genlock> must already be held by the caller
 */
void
bge_phys_update(bge_t *bgep)
{
	BGE_TRACE(("bge_phys_update($%p)", (void *)bgep));

	ASSERT(mutex_owned(bgep->genlock));
	(*bgep->physops->phys_update)(bgep);
}

#undef	BGE_DBG
#define	BGE_DBG		BGE_DBG_LINK	/* debug flag for this code	*/

/*
 * Read the link status and determine whether anything's changed ...
 *
 * This routine should be called whenever the chip flags a change
 * in the hardware link state, and repeatedly for several seconds
 * afterwards, until we're sure the state has stabilised (sometimes
 * it goes up and down several times during autonegotiation before
 * settling on the proper configuration).  This routine applies
 * timing-based heuristics to determine when the state is stable.
 *
 * This routine returns B_FALSE if the link state has not changed,
 * or if it has changed, but hasn't settled for long enough yet.  It
 * returns B_TRUE when the change to the new state should be accepted.
 * In such a case, the param_* variables give the new hardware state,
 * which the caller should use to update link_state etc.
 *
 * The caller must already hold <genlock>
 */
boolean_t
bge_phys_check(bge_t *bgep)
{
	int32_t orig_state;
	boolean_t recheck;
	boolean_t linkup;
	hrtime_t deltat;
	hrtime_t now;

	BGE_TRACE(("bge_phys_check($%p)", (void *)bgep));

	ASSERT(mutex_owned(bgep->genlock));

	linkup = bgep->param_link_up;
	orig_state = bgep->link_state;
	recheck = orig_state == LINK_STATE_UNKNOWN;
	recheck = (*bgep->physops->phys_check)(bgep, recheck);
	if (!recheck)
		return (B_FALSE);

	/*
	 * At this point, the check_*_link() function above has detected
	 * a change and updated the param_* variables to show what the
	 * latest hardware state seems to be -- but it might still be
	 * changing.
	 *
	 * The link_state must now be UNKNOWN, but if it was previously
	 * UP, we want to recognise this immediately, whereas in any other
	 * case (e.g. DOWN->UP) we don't accept it until a few seconds have
	 * elapsed, to give the hardware time to settle.
	 */
	now = gethrtime();
	deltat = now - bgep->phys_event_time;

	BGE_DEBUG(("bge_phys_check: link was %d/%s now %d/%s",
		orig_state, UPORDOWN(linkup),
		bgep->link_state, UPORDOWN(bgep->param_link_up)));
	BGE_DEBUG(("bge_phys_check: update %lld change %lld "
			"now %lld delta %lld",
		bgep->phys_write_time, bgep->phys_event_time, now, deltat));

	if (orig_state == LINK_STATE_UP)
		return (B_TRUE);
	else
		return (deltat > bgep->phys_delta_time);
}