bge_mii.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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:
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_5705:
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.
*/
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);
}