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
* 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
* 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 MII_AN_SELECTOR_8023 1
#define MII_STATUS_INVAL 0xffffU
/*
* 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 */
};
/*
* ======== 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
{
/*
* Extract the MSB of <mii_data> and shift it to the
* proper bit position in the MII-poking register
*/
dbit <<= MII_DATA_OUT_SHIFT;
/*
* Drive the bit across the wire ...
*/
}
}
/*
* Put the MDIO port in tri-state for the turn around bits
* in MII read and at end of MII management sequence.
*/
static void
{
}
/*
*/
static void
{
/* Write Preamble & Command & return to tristate */
}
static uint16_t
{
int i;
/* Check that the PHY generated a zero bit on the 2nd clock */
/* read data WORD */
for (data = 0, i = 0; i < mii_reg_size; ++i) {
data <<= 1;
}
/* leave the interface tristated */
}
/*
* ======== Next level: 16-bit PHY register access routines ========
*/
static void
{
/* Issue MII command */
command_word |= reg_dat;
}
static uint16_t
{
/* Issue MII command */
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
{
drv_usecwait(10);
drv_usecwait(10);
drv_usecwait(10);
}
/*
* Read the MII_STATUS register (BMSR)
*/
static uint16_t
{
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
{
/* Clear any latched bits by reading twice */
DMFE_DEBUG(("dmfe_probe_phy($%p, %d) BMSR 0x%x",
/*
* 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.
*/
}
static boolean_t
{
int mii_addr;
/*
* 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; ; ) {
if (dmfe_probe_phy(dmfep))
break;
if (++mii_addr > 32) {
DMFE_DEBUG(("No PHY found"));
return (B_FALSE);
}
}
/*
* DM9102A has an integrated MII interface, therefore we set
* the transceiver address kstat (KS_MII_XCVR_ADDR) to -1
*/
case OUI_DAVICOM:
return (B_TRUE);
default:
return (B_FALSE);
}
}
/*
* ======== 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.
*
*/
void
{
DMFE_DEBUG(("dmfe_update_phy: autoneg %d 100fdx %d 100hdx %d "
/*
* 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 ...
*/
if (dmfep->param_anar_100fdx)
else if (dmfep->param_anar_100hdx)
else if (dmfep->param_anar_10fdx)
if (dmfep->param_anar_100fdx)
if (dmfep->param_anar_100hdx)
if (dmfep->param_anar_10fdx)
if (dmfep->param_anar_10hdx)
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.
*/
}
/*
* 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 :-(
*/
} else if (dmfep->param_autoneg) {
/*
* Autonegotiation is only possible if loopback is OFF
*/
}
(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 ...
*/
}
/*
* 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)
/*
* 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.
*/
}
/*
* PHY initialisation, called only once
*
* Discover the MII address of the PHY (should be 1).
* Initialise according to preset NDD parameters.
* Return status
*/
{
if (ok)
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
{
/*
* Read the BMSR and check it against the previous value
*/
DMFE_DEBUG(("dmfe_check_bmsr: bmsr 0x%x -> 0x%x",
/*
* Record new value and timestamp if it's changed
*/
return (B_TRUE);
}
return (B_FALSE);
}
/*
* 'Quick' link check routine
*
* Call whenever the link state may have changed, or periodically to
* 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!
*/
{
if (dmfe_check_bmsr(dmfep))
return (B_TRUE);
}
/*
* Update all parameters and statistics after a link state change.
*/
static void
{
const char *state_msg;
const char *speed_msg;
const char *duplex_msg;
const char *link_msg;
int phy_inuse;
int ks_id;
switch (newstate) {
case LINK_UP:
state_msg = "up";
break;
default:
dmfep->param_linkup = 0;
state_msg = "down";
break;
}
switch (speed) {
case 100:
speed_msg = " 100 Mbps";
break;
case 10:
speed_msg = " 10 Mbps";
break;
default:
dmfep->op_stats_speed = 0;
speed_msg = "";
break;
}
switch (duplex) {
case GLD_DUPLEX_FULL:
duplex_msg = " Full-Duplex";
break;
case GLD_DUPLEX_HALF:
duplex_msg = " Half-Duplex";
break;
default:
duplex_msg = "";
break;
}
/*
* 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.
*/
else
DMFE_DEBUG(("dmfe_media_update: report %d link %s%s%s%s",
}
/*
* Verify and report a change in the state of the link ...
*/
static void
{
int speed;
int duplex;
switch (newstate) {
case LINK_UP:
duplex =
break;
default:
speed = 0;
switch (dmfep->link_state) {
case LINK_DOWN: /* DOWN->UNKNOWN */
break;
case LINK_UNKNOWN: /* UNKNOWN->DOWN */
break;
break;
}
break;
}
/*
* Update status & report new link state if required ...
*/
if (report)
}
/*
* 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
{
/*
* Read PHY registers & publish through driver-specific kstats
* Decode abilities & publish through ndd & standard MII kstats
*/
DMFE_DEBUG(("dmfe_process_bmsr: ANAR 0x%x->0x%x ANLPAR 0x%x SCSR 0x%x",
/*
* Capabilities of DM9102A
*/
/*
* Advertised abilities of DM9102A
*/
/*
* Link Partners advertised abilities
*/
anlpar = 0;
dmfep->param_lp_autoneg = 0;
} else {
}
/*
* Derive new state & time since last change
*/
/*
* Hah! That would be just too easy ... we have to check
* for all sorts of special cases before we decide :(
*/
/*EMPTY*/;
/*EMPTY*/;
/*
* 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 ...
*/
case GPS_LINK_STATUS|GPS_UTP_SIG:
break;
default:
break;
}
case GPS_LINK_100:
case GPS_LINK_10:
break;
default:
break;
}
}
DMFE_DEBUG(("dmfe_process_bmsr: BMSR 0x%x state %d -> %d @ %d",
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
* 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 ...
*
* should already be stopped so we're not liable to confuse them
*
*/
void
{
if (ioctl)
for (; ; now = ddi_get_lbolt()) {
}
if (again)
continue;
break;
}
break;
}
}