dmfe_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/dmfe_impl.h>
/*
* The bit-twiddling required by the MII interface makes the functions
* in this file relatively slow, so they should probably only be called
* from base/low-pri code. However, there's nothing here that really
* won't work at hi-pri, AFAIK; and 'relatively slow' only means that
* they have microsecond busy-waits all over the place.
*
* dmfe_recheck_link(), on the other hand, uses delay() and loops for
* up to dmfe_restart_time_us microseconds (currently 12 seconds), so
* it should only be called from user (ioctl) or factotum context.
*
* Time parameters:
*
* RESTART_TIME is the time in microseconds to allow for the link
* to go down and recover after changing the PHY parameters.
*
* RESTART_POLL is the interval between checks on the link state
* while waiting for up to RESTART_TIME in total.
*
* SETTLE_TIME is the time to allow for the PHY to stabilise
* after a change from LINK DOWN to LINK UP; multiple changes
* within this time are coalesced into one (in case the link
* goes UP-DOWN-UP as negotiation tries different speeds, etc).
*
* Patchable globals:
* dmfe_restart_time_us: RESTART_TIME
* dmfe_restart_poll_us: RESTART_POLL
* dmfe_mii_settle_time: SETTLE_TIME
*/
#define RESTART_POLL 600000 /* microseconds */
#define RESTART_TIME 12000000 /* microseconds */
#define SETTLE_TIME 3000000 /* microseconds */
#define MII_AN_SELECTOR_8023 1
#define MII_STATUS_INVAL 0xffffU
static clock_t dmfe_restart_poll_us = RESTART_POLL;
static clock_t dmfe_restart_time_us = RESTART_TIME;
static clock_t dmfe_mii_settle_time = SETTLE_TIME;
static const int mii_reg_size = 16; /* bits */
#define DMFE_DBG DMFE_DBG_MII /* debug flag for this code */
/*
* Type of transceiver currently in use. The IEEE 802.3 std aPhyType
* enumerates the following set
*/
enum xcvr_type {
XCVR_TYPE_UNDEFINED = 0, /* undefined, or not yet known */
XCVR_TYPE_10BASE_T = 7, /* 10 Mbps copper */
XCVR_TYPE_100BASE_X = 24 /* 100 Mbps copper */
};
/*
* ======== Lowest-level bit-twiddling to drive MII interface ========
*/
/*
* Poke <nbits> (up to 32) bits from <mii_data> along the MII control lines.
* Note: the data is taken starting with the MSB of <mii_data> and working
* down through progressively less significant bits.
*/
static void
dmfe_poke_mii(dmfe_t *dmfep, uint32_t mii_data, uint_t nbits)
{
uint32_t dbit;
ASSERT(mutex_owned(dmfep->milock));
for (; nbits > 0; mii_data <<= 1, --nbits) {
/*
* Extract the MSB of <mii_data> and shift it to the
* proper bit position in the MII-poking register
*/
dbit = mii_data >> 31;
dbit <<= MII_DATA_OUT_SHIFT;
ASSERT((dbit & ~MII_DATA_OUT) == 0);
/*
* Drive the bit across the wire ...
*/
dmfe_chip_put32(dmfep, ETHER_ROM_REG,
MII_WRITE | dbit); /* Clock Low */
drv_usecwait(MII_DELAY);
dmfe_chip_put32(dmfep, ETHER_ROM_REG,
MII_WRITE | MII_CLOCK | dbit); /* Clock High */
drv_usecwait(MII_DELAY);
}
dmfe_chip_put32(dmfep, ETHER_ROM_REG,
MII_WRITE | dbit); /* Clock Low */
drv_usecwait(MII_DELAY);
}
/*
* Put the MDIO port in tri-state for the turn around bits
* in MII read and at end of MII management sequence.
*/
static void
dmfe_tristate_mii(dmfe_t *dmfep)
{
ASSERT(mutex_owned(dmfep->milock));
dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_TRISTATE);
drv_usecwait(MII_DELAY);
dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_TRISTATE | MII_CLOCK);
drv_usecwait(MII_DELAY);
}
/*
* ======== Next level: issue an MII access command/get a response ========
*/
static void
dmfe_mii_command(dmfe_t *dmfep, uint32_t command_word, int nbits)
{
ASSERT(mutex_owned(dmfep->milock));
/* Write Preamble & Command & return to tristate */
dmfe_poke_mii(dmfep, MII_PREAMBLE, 2*mii_reg_size);
dmfe_poke_mii(dmfep, command_word, nbits);
dmfe_tristate_mii(dmfep);
}
static uint16_t
dmfe_mii_response(dmfe_t *dmfep)
{
boolean_t ack;
uint32_t data;
uint32_t tmp;
int i;
/* Check that the PHY generated a zero bit on the 2nd clock */
tmp = dmfe_chip_get32(dmfep, ETHER_ROM_REG);
ack = (tmp & MII_DATA_IN) == 0;
/* read data WORD */
for (data = 0, i = 0; i < mii_reg_size; ++i) {
dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_READ);
drv_usecwait(MII_DELAY);
dmfe_chip_put32(dmfep, ETHER_ROM_REG, MII_READ | MII_CLOCK);
drv_usecwait(MII_DELAY);
tmp = dmfe_chip_get32(dmfep, ETHER_ROM_REG);
data <<= 1;
data |= (tmp >> MII_DATA_IN_SHIFT) & 1;
}
/* leave the interface tristated */
dmfe_tristate_mii(dmfep);
return (ack ? data : ~0);
}
/*
* ======== Next level: 16-bit PHY register access routines ========
*/
static void
dmfe_phy_write(dmfe_t *dmfep, uint_t reg_num, uint_t reg_dat)
{
uint32_t command_word;
/* Issue MII command */
command_word = MII_WRITE_FRAME;
command_word |= dmfep->phy_addr << MII_PHY_ADDR_SHIFT;
command_word |= reg_num << MII_REG_ADDR_SHIFT;
command_word |= reg_dat;
dmfe_mii_command(dmfep, command_word, 2*mii_reg_size);
}
static uint16_t
dmfe_phy_read(dmfe_t *dmfep, uint_t reg_num)
{
uint32_t command_word;
/* Issue MII command */
command_word = MII_READ_FRAME;
command_word |= dmfep->phy_addr << MII_PHY_ADDR_SHIFT;
command_word |= reg_num << MII_REG_ADDR_SHIFT;
dmfe_mii_command(dmfep, command_word, mii_reg_size-2);
return (dmfe_mii_response(dmfep));
}
/*
* ======== Next level: PHY control operations ========
*/
/*
* Reset the PHYceiver, using a wierd sequence of accesses to CR12
*
* This could be done using MII accesses; but this should be quicker ....
*/
static void
dmfe_phy_reset(dmfe_t *dmfep)
{
DMFE_TRACE(("dmfe_phy_reset($%p)", (void *)dmfep));
ASSERT(mutex_owned(dmfep->milock));
dmfe_chip_put32(dmfep, PHY_STATUS_REG, GPS_WRITE_ENABLE|GPS_PHY_RESET);
drv_usecwait(10);
dmfe_chip_put32(dmfep, PHY_STATUS_REG, GPS_PHY_RESET);
drv_usecwait(10);
dmfe_chip_put32(dmfep, PHY_STATUS_REG, 0);
drv_usecwait(10);
}
/*
* Read the MII_STATUS register (BMSR)
*/
static uint16_t
dmfe_mii_status(dmfe_t *dmfep)
{
uint16_t bmsr;
bmsr = dmfe_phy_read(dmfep, MII_STATUS);
DMFE_DEBUG(("dmfe_mii_status: bmsr 0x%x", bmsr));
return (bmsr);
}
/*
* Returns true if PHY at address <phy_addr> is present and accessible.
* We determine whether the PHY is there by looking for at least one
* set bit, and at least one clear bit, in the value returned from its
* status register (i.e. BMSR is not all zeroes or all ones).
*/
static boolean_t
dmfe_probe_phy(dmfe_t *dmfep)
{
uint16_t bmsr;
ASSERT(mutex_owned(dmfep->milock));
/* Clear any latched bits by reading twice */
bmsr = dmfe_mii_status(dmfep);
bmsr = dmfe_mii_status(dmfep);
DMFE_DEBUG(("dmfe_probe_phy($%p, %d) BMSR 0x%x",
(void *)dmfep, dmfep->phy_addr, bmsr));
/*
* At least one bit in BMSR should be set (for the device
* capabilities) and at least one clear (one of the error
* bits). Unconnected devices tend to show 0xffff, but
* 0x0000 has also been seen.
*/
return (bmsr != 0 && bmsr != MII_STATUS_INVAL);
}
static boolean_t
dmfe_find_phy(dmfe_t *dmfep)
{
int mii_addr;
ASSERT(mutex_owned(dmfep->milock));
/*
* Verify that the PHY responds to MII accesses. It *should*
* be at MII address 1, but the Davicom internal PHY can be
* reprogrammed to appear at a different address, so we'll
* check all 32 possible addresses if necessary (in the order
* 1, 2, 3..31, 0)
*/
for (mii_addr = 1; ; ) {
dmfep->phy_addr = mii_addr % 32;
if (dmfe_probe_phy(dmfep))
break;
if (++mii_addr > 32) {
DMFE_DEBUG(("No PHY found"));
return (B_FALSE);
}
}
dmfep->phy_id = dmfe_phy_read(dmfep, MII_PHYIDH) << 16;
dmfep->phy_id |= dmfe_phy_read(dmfep, MII_PHYIDL);
DMFE_DEBUG(("PHY at address %d, id 0x%x", mii_addr, dmfep->phy_id));
/*
* DM9102A has an integrated MII interface, therefore we set
* the transceiver address kstat (KS_MII_XCVR_ADDR) to -1
*/
MII_KS_SET(dmfep, KS_MII_XCVR_ADDR, dmfep->phy_addr);
MII_KS_SET(dmfep, KS_MII_XCVR_ID, dmfep->phy_id);
switch (PHY_MANUFACTURER(dmfep->phy_id)) {
case OUI_DAVICOM:
return (B_TRUE);
default:
dmfe_warning(dmfep, "unsupported (non-Davicom) PHY found!");
return (B_FALSE);
}
}
#undef DMFE_DBG
#define DMFE_DBG DMFE_DBG_LINK /* debug flag for this code */
/*
* ======== Top-level PHY management routines ========
*/
/*
* (Re)initalise the PHY's speed/duplex/autonegotiation registers, basing
* the required settings on the various param_* variables that can be poked
* via the NDD interface.
*
* NOTE: the Tx/Rx processes should be STOPPED when this routine is called
*/
void
dmfe_update_phy(dmfe_t *dmfep)
{
uint16_t control;
uint16_t anar;
DMFE_DEBUG(("dmfe_update_phy: autoneg %d 100fdx %d 100hdx %d "
"10fdx %d 10hdx %d", dmfep->param_autoneg,
dmfep->param_anar_100fdx, dmfep->param_anar_100hdx,
dmfep->param_anar_10fdx, dmfep->param_anar_10hdx));
ASSERT(mutex_owned(dmfep->milock));
/*
* NDD initialisation will have already set up the param_*
* variables based on the values of the various properties.
* Here we have to transform these into the proper settings
* of the PHY registers ...
*/
anar = control = 0;
if (dmfep->param_anar_100fdx)
control |= MII_CONTROL_100MB|MII_CONTROL_FDUPLEX;
else if (dmfep->param_anar_100hdx)
control |= MII_CONTROL_100MB;
else if (dmfep->param_anar_10fdx)
control |= MII_CONTROL_FDUPLEX;
if (dmfep->param_anar_100fdx)
anar |= MII_ABILITY_100BASE_TX_FD;
if (dmfep->param_anar_100hdx)
anar |= MII_ABILITY_100BASE_TX;
if (dmfep->param_anar_10fdx)
anar |= MII_ABILITY_10BASE_T_FD;
if (dmfep->param_anar_10hdx)
anar |= MII_ABILITY_10BASE_T;
if (anar == 0) {
/*
* A stupid combination of settings has left us with no
* options - so select the default (100Mb/s half-duplex)
* for now and re-enable ALL autonegotiation options.
*/
control |= MII_CONTROL_100MB;
anar |= MII_ABILITY_100BASE_TX_FD;
anar |= MII_ABILITY_100BASE_TX;
anar |= MII_ABILITY_10BASE_T_FD;
anar |= MII_ABILITY_10BASE_T;
}
if ((dmfep->opmode & LOOPBACK_MODE_MASK) != LOOPBACK_OFF) {
/*
* If loopback is selected at the MAC level, we have
* to make sure that the settings are consistent at
* the PHY, and also keep autonegotiation switched OFF,
* otherwise we can get all sorts of strange effects
* including continuous link change interrupts :-(
*/
control |= MII_CONTROL_LOOPBACK;
} else if (dmfep->param_autoneg) {
/*
* Autonegotiation is only possible if loopback is OFF
*/
control |= MII_CONTROL_ANE;
}
DMFE_DEBUG(("dmfe_update_phy: anar 0x%x control 0x%x", anar, control));
anar |= MII_AN_SELECTOR_8023;
if ((anar != dmfep->phy_anar_w) || (control != dmfep->phy_control) ||
(dmfep->update_phy)) {
/*
* Something's changed; reset the PHY and write the new
* values to the PHY CONTROL and ANAR registers. This
* will probably 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 at the end; but in any case the ticker will keep
* an eye on what's going on ...
*/
dmfe_phy_reset(dmfep);
dmfe_phy_write(dmfep, MII_CONTROL, control);
dmfe_phy_write(dmfep, MII_AN_ADVERT, anar);
}
/*
* If autonegotiation is (now) enabled, we want to trigger
* a new autonegotiation cycle now that the PHY has been
* programmed with the capabilities to be advertised.
*/
if (control & MII_CONTROL_ANE)
dmfe_phy_write(dmfep, MII_CONTROL, control | MII_CONTROL_RSAN);
/*
* Save the values written in the shadow copies of the CONTROL
* and ANAR registers, and clear the shadow BMSR 'cos it's no
* longer valid.
*/
dmfep->phy_control = control;
dmfep->phy_anar_w = anar;
dmfep->phy_bmsr = 0;
}
/*
* PHY initialisation, called only once
*
* Discover the MII address of the PHY (should be 1).
* Initialise according to preset NDD parameters.
* Return status
*/
boolean_t
dmfe_init_phy(dmfe_t *dmfep)
{
boolean_t ok;
mutex_enter(dmfep->milock);
ok = dmfe_find_phy(dmfep);
if (ok)
dmfe_update_phy(dmfep);
mutex_exit(dmfep->milock);
return (ok);
}
/*
* ========== Active Media Determination Routines ==========
*/
/*
* Check whether the BMSR has changed. If it hasn't, this routine
* just returns B_FALSE (no further action required). Otherwise,
* it records the time when the change was seen and returns B_TRUE.
*
* This routine needs only the <milock>, although <oplock> may
* also be held. This is why full processing of the link change
* is left to dmfe_recheck_link() below.
*/
static boolean_t
dmfe_check_bmsr(dmfe_t *dmfep)
{
uint16_t new_bmsr;
DMFE_TRACE(("dmfe_check_bmsr($%p)", (void *)dmfep));
ASSERT(mutex_owned(dmfep->milock));
/*
* Read the BMSR and check it against the previous value
*/
new_bmsr = dmfe_mii_status(dmfep);
DMFE_DEBUG(("dmfe_check_bmsr: bmsr 0x%x -> 0x%x",
dmfep->phy_bmsr, new_bmsr));
/*
* Record new value and timestamp if it's changed
*/
if (new_bmsr != dmfep->phy_bmsr) {
dmfep->phy_bmsr = new_bmsr;
dmfep->phy_bmsr_lbolt = ddi_get_lbolt();
return (B_TRUE);
}
return (B_FALSE);
}
/*
* 'Quick' link check routine
*
* Call whenever the link state may have changed, or periodically to
* poll for link up/down events. Returns B_FALSE if nothing interesting
* has happened. Otherwise, it returns B_TRUE, telling the caller to
* call dmfe_recheck_link() (below). If the link state is UNKNOWN, we
* return B_TRUE anyway, even if the BMSR hasn't changed - but only after
* going through the motions, 'cos the read of the BMSR has side-effects -
* some of the BMSR bits are latching-until-read, and dmfe_check_bmsr()
* also records the time of any change to the BMSR!
*/
boolean_t
dmfe_check_link(dmfe_t *dmfep)
{
if (dmfe_check_bmsr(dmfep))
return (B_TRUE);
return (dmfep->link_state == LINK_UNKNOWN);
}
/*
* Update all parameters and statistics after a link state change.
* Also report the new state in the log and/or on the console.
*/
static void
dmfe_media_update(dmfe_t *dmfep, link_state_t newstate, int speed, int duplex)
{
const char *state_msg;
const char *speed_msg;
const char *duplex_msg;
const char *link_msg;
boolean_t report;
int phy_inuse;
int ks_id;
ASSERT(mutex_owned(dmfep->milock));
ASSERT(mutex_owned(dmfep->oplock));
ASSERT(newstate != dmfep->link_state);
switch (newstate) {
case LINK_UP:
dmfep->param_linkup = 1;
state_msg = "up";
link_msg = dmfep->link_up_msg;
dmfep->link_up_msg = "";
dmfep->link_down_msg = "";
break;
default:
dmfep->param_linkup = 0;
state_msg = "down";
link_msg = dmfep->link_down_msg;
dmfep->link_down_msg = "";
break;
}
switch (speed) {
case 100:
dmfep->op_stats_media = GLDM_100BTX;
dmfep->op_stats_speed = 100000000;
dmfep->param_speed = speed;
phy_inuse = XCVR_TYPE_100BASE_X;
speed_msg = " 100 Mbps";
break;
case 10:
dmfep->op_stats_media = GLDM_10BT;
dmfep->op_stats_speed = 10000000;
dmfep->param_speed = speed;
phy_inuse = XCVR_TYPE_10BASE_T;
speed_msg = " 10 Mbps";
break;
default:
dmfep->op_stats_media = GLDM_TP;
dmfep->op_stats_speed = 0;
phy_inuse = XCVR_TYPE_UNDEFINED;
speed_msg = "";
break;
}
MII_KS_SET(dmfep, KS_MII_XCVR_INUSE, phy_inuse);
switch (duplex) {
case GLD_DUPLEX_FULL:
duplex_msg = " Full-Duplex";
break;
case GLD_DUPLEX_HALF:
duplex_msg = " Half-Duplex";
break;
default:
duplex_msg = "";
break;
}
dmfep->op_stats_duplex = dmfep->param_duplex = duplex;
/*
* Were we expecting this change? If there's no message
* explaining why it was expected, we'll report the change
* on the console. Otherwise, it was anticipated, so we
* just log it.
*/
report = link_msg[0] == '\0';
if (newstate == LINK_UP)
ks_id = report ? KS_LINK_UP_CNT : KS_LINK_CYCLE_UP_CNT;
else
ks_id = report ? KS_LINK_DROP_CNT : KS_LINK_CYCLE_DOWN_CNT;
DRV_KS_INC(dmfep, ks_id);
(report ? dmfe_notice : dmfe_log)(dmfep, "PHY %d link %s%s%s%s",
dmfep->phy_addr, state_msg, speed_msg, duplex_msg, link_msg);
DMFE_DEBUG(("dmfe_media_update: report %d link %s%s%s%s",
report, state_msg, speed_msg, duplex_msg, link_msg));
}
/*
* Verify and report a change in the state of the link ...
*/
static void
dmfe_link_change(dmfe_t *dmfep, link_state_t newstate)
{
boolean_t report;
uint32_t gpsr;
int speed;
int duplex;
ASSERT(mutex_owned(dmfep->milock));
ASSERT(mutex_owned(dmfep->oplock));
ASSERT(newstate != dmfep->link_state);
switch (newstate) {
case LINK_UP:
MII_KS_SET(dmfep, KS_MII_LINK_UP, 1);
gpsr = dmfe_chip_get32(dmfep, PHY_STATUS_REG);
speed = gpsr & GPS_LINK_100 ? 100 : 10;
duplex =
(gpsr & GPS_FULL_DUPLEX) ? GLD_DUPLEX_FULL: GLD_DUPLEX_HALF;
report = B_TRUE;
break;
default:
speed = 0;
duplex = GLD_DUPLEX_UNKNOWN;
MII_KS_SET(dmfep, KS_MII_LINK_UP, 0);
switch (dmfep->link_state) {
case LINK_DOWN: /* DOWN->UNKNOWN */
report = dmfep->link_down_msg[0] != '\0';
break;
case LINK_UNKNOWN: /* UNKNOWN->DOWN */
report = B_FALSE;
break;
case LINK_UP: /* UP->DOWN/UNKNOWN */
report = B_TRUE;
break;
}
break;
}
MII_KS_SET(dmfep, KS_MII_LINK_DUPLEX, duplex);
/*
* Update status & report new link state if required ...
*/
if (report)
dmfe_media_update(dmfep, newstate, speed, duplex);
}
/*
* Examine the value most recently read from the BMSR and derive
* the (new) link state.
*
* This routine also incorporates heuristics determining when to
* accept a new state as valid and report it, based on the new
* (apparent) state, the old state, and the time elapsed since
* the last time we saw a (potential) state change. For example,
* we want to accept UP->DOWN immediately, but UNKNOWN->UP only
* once autonegotiation is completed and the results are stable.
*/
static link_state_t
dmfe_process_bmsr(dmfe_t *dmfep, clock_t time)
{
link_state_t newstate;
uint32_t gpsr;
uint16_t bmsr;
uint16_t anlpar;
uint16_t anar;
ASSERT(mutex_owned(dmfep->milock));
ASSERT(mutex_owned(dmfep->oplock));
/*
* Read PHY registers & publish through driver-specific kstats
* Decode abilities & publish through ndd & standard MII kstats
*/
dmfep->phy_anar_r = dmfe_phy_read(dmfep, MII_AN_ADVERT);
dmfep->phy_aner = dmfe_phy_read(dmfep, MII_AN_EXPANSION);
dmfep->phy_anlpar = dmfe_phy_read(dmfep, MII_AN_LPABLE);
dmfep->phy_dscsr = dmfe_phy_read(dmfep, DM_SCSR);
DRV_KS_SET(dmfep, KS_MIIREG_BMSR, dmfep->phy_bmsr);
DRV_KS_SET(dmfep, KS_MIIREG_ANAR, dmfep->phy_anar_r);
DRV_KS_SET(dmfep, KS_MIIREG_ANER, dmfep->phy_aner);
DRV_KS_SET(dmfep, KS_MIIREG_ANLPAR, dmfep->phy_anlpar);
DRV_KS_SET(dmfep, KS_MIIREG_DSCSR, dmfep->phy_dscsr);
DMFE_DEBUG(("dmfe_process_bmsr: ANAR 0x%x->0x%x ANLPAR 0x%x SCSR 0x%x",
dmfep->phy_anar_w, dmfep->phy_anar_r,
dmfep->phy_anlpar, dmfep->phy_dscsr));
/*
* Capabilities of DM9102A
*/
bmsr = dmfep->phy_bmsr;
dmfep->param_bmsr_100T4 = BIS(bmsr, MII_STATUS_100_BASE_T4);
dmfep->param_bmsr_100fdx = BIS(bmsr, MII_STATUS_100_BASEX_FD);
dmfep->param_bmsr_100hdx = BIS(bmsr, MII_STATUS_100_BASEX);
dmfep->param_bmsr_10fdx = BIS(bmsr, MII_STATUS_10_FD);
dmfep->param_bmsr_10hdx = BIS(bmsr, MII_STATUS_10);
dmfep->param_bmsr_remfault = 1;
dmfep->param_bmsr_autoneg = BIS(bmsr, MII_STATUS_CANAUTONEG);
MII_KS_SET(dmfep, KS_MII_CAP_100FDX, dmfep->param_bmsr_100fdx);
MII_KS_SET(dmfep, KS_MII_CAP_100HDX, dmfep->param_bmsr_100hdx);
MII_KS_SET(dmfep, KS_MII_CAP_10FDX, dmfep->param_bmsr_10fdx);
MII_KS_SET(dmfep, KS_MII_CAP_10HDX, dmfep->param_bmsr_10hdx);
MII_KS_SET(dmfep, KS_MII_CAP_REMFAULT, dmfep->param_bmsr_remfault);
MII_KS_SET(dmfep, KS_MII_CAP_AUTONEG, dmfep->param_bmsr_autoneg);
/*
* Advertised abilities of DM9102A
*/
anar = dmfep->phy_anar_r;
MII_KS_SET(dmfep, KS_MII_ADV_CAP_AUTONEG, dmfep->param_autoneg);
MII_KS_SET(dmfep, KS_MII_ADV_CAP_100FDX,
BIS(anar, MII_ABILITY_100BASE_TX_FD));
MII_KS_SET(dmfep, KS_MII_ADV_CAP_100HDX,
BIS(anar, MII_ABILITY_100BASE_TX));
MII_KS_SET(dmfep, KS_MII_ADV_CAP_10FDX,
BIS(anar, MII_ABILITY_10BASE_T_FD));
MII_KS_SET(dmfep, KS_MII_ADV_CAP_10HDX,
BIS(anar, MII_ABILITY_10BASE_T));
MII_KS_SET(dmfep, KS_MII_ADV_CAP_REMFAULT,
BIS(anar, MII_AN_ADVERT_REMFAULT));
/*
* Link Partners advertised abilities
*/
if ((dmfep->phy_aner & MII_AN_EXP_LPCANAN) == 0) {
anlpar = 0;
dmfep->param_lp_autoneg = 0;
} else {
anlpar = dmfep->phy_anlpar;
dmfep->param_lp_autoneg = 1;
}
dmfep->param_lp_100T4 = BIS(anlpar, MII_ABILITY_100BASE_T4);
dmfep->param_lp_100fdx = BIS(anlpar, MII_ABILITY_100BASE_TX_FD);
dmfep->param_lp_100hdx = BIS(anlpar, MII_ABILITY_100BASE_TX);
dmfep->param_lp_10fdx = BIS(anlpar, MII_ABILITY_10BASE_T_FD);
dmfep->param_lp_10hdx = BIS(anlpar, MII_ABILITY_10BASE_T);
dmfep->param_lp_remfault = BIS(anlpar, MII_AN_ADVERT_REMFAULT);
MII_KS_SET(dmfep, KS_MII_LP_CAP_100FDX, dmfep->param_lp_100fdx);
MII_KS_SET(dmfep, KS_MII_LP_CAP_100HDX, dmfep->param_lp_100hdx);
MII_KS_SET(dmfep, KS_MII_LP_CAP_10FDX, dmfep->param_lp_10fdx);
MII_KS_SET(dmfep, KS_MII_LP_CAP_10HDX, dmfep->param_lp_10hdx);
MII_KS_SET(dmfep, KS_MII_LP_CAP_AUTONEG, dmfep->param_lp_autoneg);
MII_KS_SET(dmfep, KS_MII_LP_CAP_REMFAULT, dmfep->param_lp_remfault);
/*
* Derive new state & time since last change
*/
newstate = (dmfep->phy_bmsr & MII_STATUS_LINKUP) ? LINK_UP : LINK_DOWN;
time -= dmfep->phy_bmsr_lbolt;
/*
* Hah! That would be just too easy ... we have to check
* for all sorts of special cases before we decide :(
*/
if (dmfep->phy_bmsr == MII_STATUS_INVAL)
newstate = LINK_DOWN;
else if (dmfep->link_state == LINK_UP && newstate == LINK_DOWN)
/*EMPTY*/;
else if (time < drv_usectohz(dmfe_mii_settle_time))
newstate = LINK_UNKNOWN;
else if (dmfep->phy_bmsr & MII_STATUS_ANDONE)
/*EMPTY*/;
else if (dmfep->phy_control & MII_CONTROL_ANE)
newstate = LINK_UNKNOWN;
if (newstate == LINK_UP) {
/*
* Link apparently UP - but get the PHY status register
* (GPSR) and make sure it also shows a consistent value.
* In particular, both the link status bits should be 1,
* and the speed bits should show one set and one clear.
* Any other combination indicates that we haven't really
* got a stable link yet ...
*/
gpsr = dmfe_chip_get32(dmfep, PHY_STATUS_REG);
DMFE_DEBUG(("dmfe_process_bmsr: GPSR 0x%x", gpsr));
switch (gpsr & (GPS_LINK_STATUS|GPS_UTP_SIG)) {
case GPS_LINK_STATUS|GPS_UTP_SIG:
break;
default:
newstate = LINK_UNKNOWN;
break;
}
switch (gpsr & (GPS_LINK_10|GPS_LINK_100)) {
case GPS_LINK_100:
case GPS_LINK_10:
break;
default:
newstate = LINK_UNKNOWN;
break;
}
}
DMFE_DEBUG(("dmfe_process_bmsr: BMSR 0x%x state %d -> %d @ %d",
dmfep->phy_bmsr, dmfep->link_state, newstate, time));
return (newstate);
}
/*
* 'Full' link check routine
*
* Call whenever dmfe_check_link() above indicates that the link
* state may have changed. Handles all changes to the link state
* (up/down, speed/duplex changes), including multiple changes
* occuring within the <timeout>. <timeout> will be zero if called
* from the factotum (for an unexpected change) or the number of
* ticks for which to wait for stability after an ioctl that changes
* the link parameters. Even when <timeout> is zero, we loop while
* the BMSR keeps changing ...
*
* Needs both <milock> and <oplock>, and the Tx/Rx processes
* should already be stopped so we're not liable to confuse them
* by changing the PHY/MAC parameters under them ...
*
*/
void
dmfe_recheck_link(dmfe_t *dmfep, boolean_t ioctl)
{
link_state_t newstate;
boolean_t again;
clock_t deadline;
clock_t now;
DMFE_TRACE(("dmfe_recheck_link($%p, %d)", (void *)dmfep, ioctl));
ASSERT(mutex_owned(dmfep->milock));
ASSERT(mutex_owned(dmfep->oplock));
now = deadline = ddi_get_lbolt();
if (ioctl)
deadline += drv_usectohz(dmfe_restart_time_us);
for (; ; now = ddi_get_lbolt()) {
newstate = dmfe_process_bmsr(dmfep, now);
again = dmfe_check_bmsr(dmfep);
if (newstate != dmfep->link_state) {
dmfe_link_change(dmfep, newstate);
dmfep->link_state = newstate;
again = B_TRUE;
}
ASSERT(dmfep->link_state == newstate);
if (again)
continue;
if (newstate == LINK_UP) {
dmfep->update_phy = B_TRUE;
break;
}
if (now >= deadline)
break;
delay(drv_usectohz(dmfe_restart_poll_us));
}
}
#undef DMFE_DBG