NetBSD/sys/dev/ic/smc91cxx.c
pooka 7fe36dcdc1 Instead of writing the packet length in two 8bit pieces, use one
16bit write.  At least in certain environments this has the effect
of the latter 8bit write of the length to be not interpreted as
the first 16 bits of the packet contents and hence the interface
working.
2005-06-06 20:20:09 +00:00

1625 lines
38 KiB
C

/* $NetBSD: smc91cxx.c,v 1.52 2005/06/06 20:20:09 pooka Exp $ */
/*-
* Copyright (c) 1997 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
* NASA Ames Research Center.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 1996 Gardner Buchanan <gbuchanan@shl.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Gardner Buchanan.
* 4. The name of Gardner Buchanan may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* from FreeBSD Id: if_sn.c,v 1.4 1996/03/18 15:47:16 gardner Exp
*/
/*
* Core driver for the SMC 91Cxx family of Ethernet chips.
*
* Memory allocation interrupt logic is drived from an SMC 91C90 driver
* written for NetBSD/amiga by Michael Hitch.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: smc91cxx.c,v 1.52 2005/06/06 20:20:09 pooka Exp $");
#include "opt_inet.h"
#include "opt_ccitt.h"
#include "opt_llc.h"
#include "opt_ns.h"
#include "bpfilter.h"
#include "rnd.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/syslog.h>
#include <sys/socket.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#if NRND > 0
#include <sys/rnd.h>
#endif
#include <machine/bus.h>
#include <machine/intr.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/if_inarp.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#endif
#ifdef NS
#include <netns/ns.h>
#include <netns/ns_if.h>
#endif
#if defined(CCITT) && defined(LLC)
#include <sys/socketvar.h>
#include <netccitt/x25.h>
#include <netccitt/pk.h>
#include <netccitt/pk_var.h>
#include <netccitt/pk_extern.h>
#endif
#if NBPFILTER > 0
#include <net/bpf.h>
#include <net/bpfdesc.h>
#endif
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/mii/mii_bitbang.h>
#include <dev/ic/smc91cxxreg.h>
#include <dev/ic/smc91cxxvar.h>
#ifndef __BUS_SPACE_HAS_STREAM_METHODS
#define bus_space_write_multi_stream_2 bus_space_write_multi_2
#define bus_space_write_multi_stream_4 bus_space_write_multi_4
#define bus_space_read_multi_stream_2 bus_space_read_multi_2
#define bus_space_read_multi_stream_4 bus_space_read_multi_4
#define bus_space_write_stream_4 bus_space_write_4
#define bus_space_read_stream_4 bus_space_read_4
#endif /* __BUS_SPACE_HAS_STREAM_METHODS */
/* XXX Hardware padding doesn't work yet(?) */
#define SMC91CXX_SW_PAD
const char *smc91cxx_idstrs[] = {
NULL, /* 0 */
NULL, /* 1 */
NULL, /* 2 */
"SMC91C90/91C92", /* 3 */
"SMC91C94/91C96", /* 4 */
"SMC91C95", /* 5 */
NULL, /* 6 */
"SMC91C100", /* 7 */
"SMC91C100FD", /* 8 */
"SMC91C111", /* 9 */
NULL, /* 10 */
NULL, /* 11 */
NULL, /* 12 */
NULL, /* 13 */
NULL, /* 14 */
NULL, /* 15 */
};
/* Supported media types. */
const int smc91cxx_media[] = {
IFM_ETHER|IFM_10_T,
IFM_ETHER|IFM_10_5,
};
#define NSMC91CxxMEDIA (sizeof(smc91cxx_media) / sizeof(smc91cxx_media[0]))
/*
* MII bit-bang glue.
*/
u_int32_t smc91cxx_mii_bitbang_read(struct device *);
void smc91cxx_mii_bitbang_write(struct device *, u_int32_t);
const struct mii_bitbang_ops smc91cxx_mii_bitbang_ops = {
smc91cxx_mii_bitbang_read,
smc91cxx_mii_bitbang_write,
{
MR_MDO, /* MII_BIT_MDO */
MR_MDI, /* MII_BIT_MDI */
MR_MCLK, /* MII_BIT_MDC */
MR_MDOE, /* MII_BIT_DIR_HOST_PHY */
0, /* MII_BIT_DIR_PHY_HOST */
}
};
/* MII callbacks */
int smc91cxx_mii_readreg(struct device *, int, int);
void smc91cxx_mii_writereg(struct device *, int, int, int);
void smc91cxx_statchg(struct device *);
void smc91cxx_tick(void *);
int smc91cxx_mediachange(struct ifnet *);
void smc91cxx_mediastatus(struct ifnet *, struct ifmediareq *);
int smc91cxx_set_media(struct smc91cxx_softc *, int);
void smc91cxx_init(struct smc91cxx_softc *);
void smc91cxx_read(struct smc91cxx_softc *);
void smc91cxx_reset(struct smc91cxx_softc *);
void smc91cxx_start(struct ifnet *);
void smc91cxx_copy_tx_frame(struct smc91cxx_softc *, struct mbuf *);
void smc91cxx_resume(struct smc91cxx_softc *);
void smc91cxx_stop(struct smc91cxx_softc *);
void smc91cxx_watchdog(struct ifnet *);
int smc91cxx_ioctl(struct ifnet *, u_long, caddr_t);
static __inline int ether_cmp(void *, void *);
static __inline int
ether_cmp(va, vb)
void *va, *vb;
{
u_int8_t *a = va;
u_int8_t *b = vb;
return ((a[5] != b[5]) || (a[4] != b[4]) || (a[3] != b[3]) ||
(a[2] != b[2]) || (a[1] != b[1]) || (a[0] != b[0]));
}
void
smc91cxx_attach(sc, myea)
struct smc91cxx_softc *sc;
u_int8_t *myea;
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct ifmedia *ifm = &sc->sc_mii.mii_media;
const char *idstr;
u_int32_t miicapabilities;
u_int16_t tmp;
u_int8_t enaddr[ETHER_ADDR_LEN];
int i, aui, mult, scale, memsize;
char pbuf[9];
tmp = bus_space_read_2(bst, bsh, BANK_SELECT_REG_W);
/* check magic number */
if ((tmp & BSR_DETECT_MASK) != BSR_DETECT_VALUE) {
aprint_error("%s: failed to detect chip, bsr=%04x\n",
sc->sc_dev.dv_xname, tmp);
return;
}
/* Make sure the chip is stopped. */
smc91cxx_stop(sc);
SMC_SELECT_BANK(sc, 3);
tmp = bus_space_read_2(bst, bsh, REVISION_REG_W);
sc->sc_chipid = RR_ID(tmp);
idstr = smc91cxx_idstrs[sc->sc_chipid];
aprint_normal("%s: ", sc->sc_dev.dv_xname);
if (idstr != NULL)
aprint_normal("%s, ", idstr);
else
aprint_normal("unknown chip id %d, ", sc->sc_chipid);
aprint_normal("revision %d, ", RR_REV(tmp));
SMC_SELECT_BANK(sc, 0);
switch (sc->sc_chipid) {
default:
mult = MCR_MEM_MULT(bus_space_read_2(bst, bsh, MEM_CFG_REG_W));
scale = MIR_SCALE_91C9x;
break;
case CHIP_91C111:
mult = MIR_MULT_91C111;
scale = MIR_SCALE_91C111;
}
memsize = bus_space_read_2(bst, bsh, MEM_INFO_REG_W) & MIR_TOTAL_MASK;
if (memsize == 255) memsize++;
memsize *= scale * mult;
format_bytes(pbuf, sizeof(pbuf), memsize);
aprint_normal("buffer size: %s\n", pbuf);
/* Read the station address from the chip. */
SMC_SELECT_BANK(sc, 1);
if (myea == NULL) {
myea = enaddr;
for (i = 0; i < ETHER_ADDR_LEN; i += 2) {
tmp = bus_space_read_2(bst, bsh, IAR_ADDR0_REG_W + i);
myea[i + 1] = (tmp >> 8) & 0xff;
myea[i] = tmp & 0xff;
}
}
aprint_normal("%s: MAC address %s, ", sc->sc_dev.dv_xname,
ether_sprintf(myea));
/* Initialize the ifnet structure. */
strcpy(ifp->if_xname, sc->sc_dev.dv_xname);
ifp->if_softc = sc;
ifp->if_start = smc91cxx_start;
ifp->if_ioctl = smc91cxx_ioctl;
ifp->if_watchdog = smc91cxx_watchdog;
ifp->if_flags =
IFF_BROADCAST | IFF_SIMPLEX | IFF_NOTRAILERS | IFF_MULTICAST;
IFQ_SET_READY(&ifp->if_snd);
/* Attach the interface. */
if_attach(ifp);
ether_ifattach(ifp, myea);
/*
* Initialize our media structures and MII info. We will
* probe the MII if we are on the SMC91Cxx
*/
sc->sc_mii.mii_ifp = ifp;
sc->sc_mii.mii_readreg = smc91cxx_mii_readreg;
sc->sc_mii.mii_writereg = smc91cxx_mii_writereg;
sc->sc_mii.mii_statchg = smc91cxx_statchg;
ifmedia_init(ifm, IFM_IMASK, smc91cxx_mediachange, smc91cxx_mediastatus);
SMC_SELECT_BANK(sc, 1);
tmp = bus_space_read_2(bst, bsh, CONFIG_REG_W);
miicapabilities = BMSR_MEDIAMASK|BMSR_ANEG;
switch (sc->sc_chipid) {
case CHIP_91100:
/*
* The 91100 does not have full-duplex capabilities,
* even if the PHY does.
*/
miicapabilities &= ~(BMSR_100TXFDX | BMSR_10TFDX);
case CHIP_91100FD:
case CHIP_91C111:
if (tmp & CR_MII_SELECT) {
aprint_normal("default media MII");
if (sc->sc_chipid == CHIP_91C111) {
aprint_normal(" (%s PHY)\n", (tmp & CR_AUI_SELECT) ?
"external" : "internal");
sc->sc_internal_phy = !(tmp & CR_AUI_SELECT);
} else
aprint_normal("\n");
mii_attach(&sc->sc_dev, &sc->sc_mii, miicapabilities,
MII_PHY_ANY, MII_OFFSET_ANY, 0);
if (LIST_FIRST(&sc->sc_mii.mii_phys) == NULL) {
ifmedia_add(&sc->sc_mii.mii_media,
IFM_ETHER|IFM_NONE, 0, NULL);
ifmedia_set(&sc->sc_mii.mii_media,
IFM_ETHER|IFM_NONE);
} else {
ifmedia_set(&sc->sc_mii.mii_media,
IFM_ETHER|IFM_AUTO);
}
sc->sc_flags |= SMC_FLAGS_HAS_MII;
break;
} else
if (sc->sc_chipid == CHIP_91C111) {
/*
* XXX: Should bring it out of low-power mode
*/
aprint_normal("EPH interface in low power mode\n");
sc->sc_internal_phy = 0;
return;
}
/*FALLTHROUGH*/
default:
aprint_normal("default media %s\n", (aui = (tmp & CR_AUI_SELECT)) ?
"AUI" : "UTP");
for (i = 0; i < NSMC91CxxMEDIA; i++)
ifmedia_add(ifm, smc91cxx_media[i], 0, NULL);
ifmedia_set(ifm, IFM_ETHER | (aui ? IFM_10_5 : IFM_10_T));
break;
}
#if NRND > 0
rnd_attach_source(&sc->rnd_source, sc->sc_dev.dv_xname,
RND_TYPE_NET, 0);
#endif
/* The attach is successful. */
sc->sc_flags |= SMC_FLAGS_ATTACHED;
}
/*
* Change media according to request.
*/
int
smc91cxx_mediachange(ifp)
struct ifnet *ifp;
{
struct smc91cxx_softc *sc = ifp->if_softc;
return (smc91cxx_set_media(sc, sc->sc_mii.mii_media.ifm_media));
}
int
smc91cxx_set_media(sc, media)
struct smc91cxx_softc *sc;
int media;
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
u_int16_t tmp;
/*
* If the interface is not currently powered on, just return.
* When it is enabled later, smc91cxx_init() will properly set
* up the media for us.
*/
if ((sc->sc_flags & SMC_FLAGS_ENABLED) == 0)
return (0);
if (IFM_TYPE(media) != IFM_ETHER)
return (EINVAL);
if (sc->sc_flags & SMC_FLAGS_HAS_MII)
return (mii_mediachg(&sc->sc_mii));
switch (IFM_SUBTYPE(media)) {
case IFM_10_T:
case IFM_10_5:
SMC_SELECT_BANK(sc, 1);
tmp = bus_space_read_2(bst, bsh, CONFIG_REG_W);
if (IFM_SUBTYPE(media) == IFM_10_5)
tmp |= CR_AUI_SELECT;
else
tmp &= ~CR_AUI_SELECT;
bus_space_write_2(bst, bsh, CONFIG_REG_W, tmp);
delay(20000); /* XXX is this needed? */
break;
default:
return (EINVAL);
}
return (0);
}
/*
* Notify the world which media we're using.
*/
void
smc91cxx_mediastatus(ifp, ifmr)
struct ifnet *ifp;
struct ifmediareq *ifmr;
{
struct smc91cxx_softc *sc = ifp->if_softc;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
u_int16_t tmp;
if ((sc->sc_flags & SMC_FLAGS_ENABLED) == 0) {
ifmr->ifm_active = IFM_ETHER | IFM_NONE;
ifmr->ifm_status = 0;
return;
}
/*
* If we have MII, go ask the PHY what's going on.
*/
if (sc->sc_flags & SMC_FLAGS_HAS_MII) {
mii_pollstat(&sc->sc_mii);
ifmr->ifm_active = sc->sc_mii.mii_media_active;
ifmr->ifm_status = sc->sc_mii.mii_media_status;
return;
}
SMC_SELECT_BANK(sc, 1);
tmp = bus_space_read_2(bst, bsh, CONFIG_REG_W);
ifmr->ifm_active =
IFM_ETHER | ((tmp & CR_AUI_SELECT) ? IFM_10_5 : IFM_10_T);
}
/*
* Reset and initialize the chip.
*/
void
smc91cxx_init(sc)
struct smc91cxx_softc *sc;
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
u_int16_t tmp;
u_int8_t *enaddr;
int s, i;
s = splnet();
/*
* This resets the registers mostly to defaults, but doesn't
* affect the EEPROM. After the reset cycle, we pause briefly
* for the chip to recover.
*
* XXX how long are we really supposed to delay? --thorpej
*/
SMC_SELECT_BANK(sc, 0);
bus_space_write_2(bst, bsh, RECV_CONTROL_REG_W, RCR_SOFTRESET);
delay(100);
bus_space_write_2(bst, bsh, RECV_CONTROL_REG_W, 0);
delay(200);
bus_space_write_2(bst, bsh, TXMIT_CONTROL_REG_W, 0);
/* Set the Ethernet address. */
SMC_SELECT_BANK(sc, 1);
enaddr = (u_int8_t *)LLADDR(ifp->if_sadl);
for (i = 0; i < ETHER_ADDR_LEN; i += 2) {
tmp = enaddr[i + 1] << 8 | enaddr[i];
bus_space_write_2(bst, bsh, IAR_ADDR0_REG_W + i, tmp);
}
/*
* Set the control register to automatically release successfully
* transmitted packets (making the best use of our limited memory)
* and enable the EPH interrupt on certain TX errors.
*/
bus_space_write_2(bst, bsh, CONTROL_REG_W, (CTR_AUTO_RELEASE |
CTR_TE_ENABLE | CTR_CR_ENABLE | CTR_LE_ENABLE));
/*
* Reset the MMU and wait for it to be un-busy.
*/
SMC_SELECT_BANK(sc, 2);
bus_space_write_2(bst, bsh, MMU_CMD_REG_W, MMUCR_RESET);
for (;;) {
tmp = bus_space_read_2(bst, bsh, MMU_CMD_REG_W);
if (tmp == 0xffff) /* card went away! */
return;
if ((tmp & MMUCR_BUSY) == 0)
break;
}
/*
* Disable all interrupts.
*/
bus_space_write_1(bst, bsh, INTR_MASK_REG_B, 0);
/*
* On the 91c111, enable auto-negotiation, and set the LED
* status pins to something sane.
* XXX: Should be some way for MD code to decide the latter.
*/
SMC_SELECT_BANK(sc, 0);
if (sc->sc_chipid == CHIP_91C111) {
bus_space_write_2(bst, bsh, RX_PHY_CONTROL_REG_W,
RPC_ANEG |
(RPC_LS_LINK_DETECT << RPC_LSA_SHIFT) |
(RPC_LS_TXRX << RPC_LSB_SHIFT));
}
/*
* Set current media.
*/
smc91cxx_set_media(sc, sc->sc_mii.mii_media.ifm_cur->ifm_media);
/*
* Set the receive filter. We want receive enable and auto
* strip of CRC from received packet. If we are in promisc. mode,
* then set that bit as well.
*
* XXX Initialize multicast filter. For now, we just accept
* XXX all multicast.
*/
SMC_SELECT_BANK(sc, 0);
tmp = RCR_ENABLE | RCR_STRIP_CRC | RCR_ALMUL;
if (ifp->if_flags & IFF_PROMISC)
tmp |= RCR_PROMISC;
bus_space_write_2(bst, bsh, RECV_CONTROL_REG_W, tmp);
/*
* Set transmitter control to "enabled".
*/
tmp = TCR_ENABLE;
#ifndef SMC91CXX_SW_PAD
/*
* Enable hardware padding of transmitted packets.
* XXX doesn't work?
*/
tmp |= TCR_PAD_ENABLE;
#endif
bus_space_write_2(bst, bsh, TXMIT_CONTROL_REG_W, tmp);
/*
* Now, enable interrupts.
*/
SMC_SELECT_BANK(sc, 2);
if (sc->sc_chipid == CHIP_91C111 && sc->sc_internal_phy) {
bus_space_write_1(bst, bsh, INTR_MASK_REG_B,
IM_EPH_INT | IM_RX_OVRN_INT |
IM_RCV_INT | IM_TX_INT | IM_MD_INT);
} else {
bus_space_write_1(bst, bsh, INTR_MASK_REG_B,
IM_EPH_INT | IM_RX_OVRN_INT | IM_RCV_INT | IM_TX_INT);
}
/* Interface is now running, with no output active. */
ifp->if_flags |= IFF_RUNNING;
ifp->if_flags &= ~IFF_OACTIVE;
if (sc->sc_flags & SMC_FLAGS_HAS_MII) {
/* Start the one second clock. */
callout_reset(&sc->sc_mii_callout, hz, smc91cxx_tick, sc);
}
/*
* Attempt to start any pending transmission.
*/
smc91cxx_start(ifp);
splx(s);
}
/*
* Start output on an interface.
* Must be called at splnet or interrupt level.
*/
void
smc91cxx_start(ifp)
struct ifnet *ifp;
{
struct smc91cxx_softc *sc = ifp->if_softc;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
u_int len;
struct mbuf *m;
u_int16_t length, npages;
u_int8_t packetno;
int timo, pad;
if ((ifp->if_flags & (IFF_RUNNING|IFF_OACTIVE)) != IFF_RUNNING)
return;
again:
/*
* Peek at the next packet.
*/
IFQ_POLL(&ifp->if_snd, m);
if (m == NULL)
return;
/*
* Compute the frame length and set pad to give an overall even
* number of bytes. Below, we assume that the packet length
* is even.
*/
for (len = 0; m != NULL; m = m->m_next)
len += m->m_len;
pad = (len & 1);
/*
* We drop packets that are too large. Perhaps we should
* truncate them instead?
*/
if ((len + pad) > (ETHER_MAX_LEN - ETHER_CRC_LEN)) {
printf("%s: large packet discarded\n", sc->sc_dev.dv_xname);
ifp->if_oerrors++;
IFQ_DEQUEUE(&ifp->if_snd, m);
m_freem(m);
goto readcheck;
}
#ifdef SMC91CXX_SW_PAD
/*
* Not using hardware padding; pad to ETHER_MIN_LEN.
*/
if (len < (ETHER_MIN_LEN - ETHER_CRC_LEN))
pad = ETHER_MIN_LEN - ETHER_CRC_LEN - len;
#endif
length = pad + len;
/*
* The MMU has a 256 byte page size. The MMU expects us to
* ask for "npages - 1". We include space for the status word,
* byte count, and control bytes in the allocation request.
*/
npages = (length + 6) >> 8;
/*
* Now allocate the memory.
*/
SMC_SELECT_BANK(sc, 2);
bus_space_write_2(bst, bsh, MMU_CMD_REG_W, MMUCR_ALLOC | npages);
timo = MEMORY_WAIT_TIME;
do {
if (bus_space_read_1(bst, bsh, INTR_STAT_REG_B) & IM_ALLOC_INT)
break;
delay(1);
} while (--timo);
packetno = bus_space_read_1(bst, bsh, ALLOC_RESULT_REG_B);
if (packetno & ARR_FAILED || timo == 0) {
/*
* No transmit memory is available. Record the number
* of requestd pages and enable the allocation completion
* interrupt. Set up the watchdog timer in case we miss
* the interrupt. Mark the interface as active so that
* no one else attempts to transmit while we're allocating
* memory.
*/
bus_space_write_1(bst, bsh, INTR_MASK_REG_B,
bus_space_read_1(bst, bsh, INTR_MASK_REG_B) | IM_ALLOC_INT);
ifp->if_timer = 5;
ifp->if_flags |= IFF_OACTIVE;
return;
}
/*
* We have a packet number - set the data window.
*/
bus_space_write_1(bst, bsh, PACKET_NUM_REG_B, packetno);
/*
* Point to the beginning of the packet.
*/
bus_space_write_2(bst, bsh, POINTER_REG_W, PTR_AUTOINC /* | 0x0000 */);
/*
* Send the packet length (+6 for stats, length, and control bytes)
* and the status word (set to zeros).
*/
bus_space_write_2(bst, bsh, DATA_REG_W, 0);
bus_space_write_2(bst, bsh, DATA_REG_W, (length + 6) & 0x7ff);
/*
* Get the packet from the kernel. This will include the Ethernet
* frame header, MAC address, etc.
*/
IFQ_DEQUEUE(&ifp->if_snd, m);
/*
* Push the packet out to the card.
*/
smc91cxx_copy_tx_frame(sc, m);
#ifdef SMC91CXX_SW_PAD
/*
* Push out padding.
*/
while (pad > 1) {
bus_space_write_2(bst, bsh, DATA_REG_W, 0);
pad -= 2;
}
if (pad)
bus_space_write_1(bst, bsh, DATA_REG_B, 0);
#endif
/*
* Push out control byte and unused packet byte. The control byte
* is 0, meaning the packet is even lengthed and no special
* CRC handling is necessary.
*/
bus_space_write_2(bst, bsh, DATA_REG_W, 0);
/*
* Enable transmit interrupts and let the chip go. Set a watchdog
* in case we miss the interrupt.
*/
bus_space_write_1(bst, bsh, INTR_MASK_REG_B,
bus_space_read_1(bst, bsh, INTR_MASK_REG_B) |
IM_TX_INT | IM_TX_EMPTY_INT);
bus_space_write_2(bst, bsh, MMU_CMD_REG_W, MMUCR_ENQUEUE);
ifp->if_timer = 5;
#if NBPFILTER > 0
/* Hand off a copy to the bpf. */
if (ifp->if_bpf)
bpf_mtap(ifp->if_bpf, m);
#endif
ifp->if_opackets++;
m_freem(m);
readcheck:
/*
* Check for incoming pcakets. We don't want to overflow the small
* RX FIFO. If nothing has arrived, attempt to queue another
* transmit packet.
*/
if (bus_space_read_2(bst, bsh, FIFO_PORTS_REG_W) & FIFO_REMPTY)
goto again;
}
/*
* Squirt a (possibly misaligned) mbuf to the device
*/
void
smc91cxx_copy_tx_frame(sc, m0)
struct smc91cxx_softc *sc;
struct mbuf *m0;
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct mbuf *m;
int len, leftover;
u_int16_t dbuf;
u_int8_t *p;
#ifdef DIAGNOSTIC
u_int8_t *lim;
#endif
/* start out with no leftover data */
leftover = 0;
dbuf = 0;
/* Process the chain of mbufs */
for (m = m0; m != NULL; m = m->m_next) {
/*
* Process all of the data in a single mbuf.
*/
p = mtod(m, u_int8_t *);
len = m->m_len;
#ifdef DIAGNOSTIC
lim = p + len;
#endif
while (len > 0) {
if (leftover) {
/*
* Data left over (from mbuf or realignment).
* Buffer the next byte, and write it and
* the leftover data out.
*/
dbuf |= *p++ << 8;
len--;
bus_space_write_2(bst, bsh, DATA_REG_W, dbuf);
leftover = 0;
} else if ((long) p & 1) {
/*
* Misaligned data. Buffer the next byte.
*/
dbuf = *p++;
len--;
leftover = 1;
} else {
/*
* Aligned data. This is the case we like.
*
* Write-region out as much as we can, then
* buffer the remaining byte (if any).
*/
leftover = len & 1;
len &= ~1;
bus_space_write_multi_stream_2(bst, bsh,
DATA_REG_W, (u_int16_t *)p, len >> 1);
p += len;
if (leftover)
dbuf = *p++;
len = 0;
}
}
if (len < 0)
panic("smc91cxx_copy_tx_frame: negative len");
#ifdef DIAGNOSTIC
if (p != lim)
panic("smc91cxx_copy_tx_frame: p != lim");
#endif
}
if (leftover)
bus_space_write_1(bst, bsh, DATA_REG_B, dbuf);
}
/*
* Interrupt service routine.
*/
int
smc91cxx_intr(arg)
void *arg;
{
struct smc91cxx_softc *sc = arg;
struct ifnet *ifp = &sc->sc_ec.ec_if;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
u_int8_t mask, interrupts, status;
u_int16_t packetno, tx_status, card_stats;
if ((sc->sc_flags & SMC_FLAGS_ENABLED) == 0 ||
(sc->sc_dev.dv_flags & DVF_ACTIVE) == 0)
return (0);
SMC_SELECT_BANK(sc, 2);
/*
* Obtain the current interrupt mask.
*/
mask = bus_space_read_1(bst, bsh, INTR_MASK_REG_B);
/*
* Get the set of interrupt which occurred and eliminate any
* which are not enabled.
*/
interrupts = bus_space_read_1(bst, bsh, INTR_STAT_REG_B);
status = interrupts & mask;
/* Ours? */
if (status == 0)
return (0);
/*
* It's ours; disable all interrupts while we process them.
*/
bus_space_write_1(bst, bsh, INTR_MASK_REG_B, 0);
/*
* Receive overrun interrupts.
*/
if (status & IM_RX_OVRN_INT) {
bus_space_write_1(bst, bsh, INTR_ACK_REG_B, IM_RX_OVRN_INT);
ifp->if_ierrors++;
}
/*
* Receive interrupts.
*/
if (status & IM_RCV_INT) {
#if 1 /* DIAGNOSTIC */
packetno = bus_space_read_2(bst, bsh, FIFO_PORTS_REG_W);
if (packetno & FIFO_REMPTY) {
printf("%s: receive interrupt on empty fifo\n",
sc->sc_dev.dv_xname);
goto out;
} else
#endif
smc91cxx_read(sc);
}
/*
* Memory allocation interrupts.
*/
if (status & IM_ALLOC_INT) {
/* Disable this interrupt. */
mask &= ~IM_ALLOC_INT;
/*
* Release the just-allocated memory. We will reallocate
* it through the normal start logic.
*/
while (bus_space_read_2(bst, bsh, MMU_CMD_REG_W) & MMUCR_BUSY)
/* XXX bound this loop! */ ;
bus_space_write_2(bst, bsh, MMU_CMD_REG_W, MMUCR_FREEPKT);
ifp->if_flags &= ~IFF_OACTIVE;
ifp->if_timer = 0;
}
/*
* Transmit complete interrupt. Handle transmission error messages.
* This will only be called on error condition because of AUTO RELEASE
* mode.
*/
if (status & IM_TX_INT) {
bus_space_write_1(bst, bsh, INTR_ACK_REG_B, IM_TX_INT);
packetno = bus_space_read_2(bst, bsh, FIFO_PORTS_REG_W) &
FIFO_TX_MASK;
/*
* Select this as the packet to read from.
*/
bus_space_write_1(bst, bsh, PACKET_NUM_REG_B, packetno);
/*
* Position the pointer to the beginning of the packet.
*/
bus_space_write_2(bst, bsh, POINTER_REG_W,
PTR_AUTOINC | PTR_READ /* | 0x0000 */);
/*
* Fetch the TX status word. This will be a copy of
* the EPH_STATUS_REG_W at the time of the transmission
* failure.
*/
tx_status = bus_space_read_2(bst, bsh, DATA_REG_W);
if (tx_status & EPHSR_TX_SUC)
printf("%s: successful packet caused TX interrupt?!\n",
sc->sc_dev.dv_xname);
else
ifp->if_oerrors++;
if (tx_status & EPHSR_LATCOL)
ifp->if_collisions++;
/*
* Some of these errors disable the transmitter; reenable it.
*/
SMC_SELECT_BANK(sc, 0);
#ifdef SMC91CXX_SW_PAD
bus_space_write_2(bst, bsh, TXMIT_CONTROL_REG_W, TCR_ENABLE);
#else
bus_space_write_2(bst, bsh, TXMIT_CONTROL_REG_W,
TCR_ENABLE | TCR_PAD_ENABLE);
#endif
/*
* Kill the failed packet and wait for the MMU to unbusy.
*/
SMC_SELECT_BANK(sc, 2);
while (bus_space_read_2(bst, bsh, MMU_CMD_REG_W) & MMUCR_BUSY)
/* XXX bound this loop! */ ;
bus_space_write_2(bst, bsh, MMU_CMD_REG_W, MMUCR_FREEPKT);
ifp->if_timer = 0;
}
/*
* Transmit underrun interrupts. We use this opportunity to
* update transmit statistics from the card.
*/
if (status & IM_TX_EMPTY_INT) {
bus_space_write_1(bst, bsh, INTR_ACK_REG_B, IM_TX_EMPTY_INT);
/* Disable this interrupt. */
mask &= ~IM_TX_EMPTY_INT;
SMC_SELECT_BANK(sc, 0);
card_stats = bus_space_read_2(bst, bsh, COUNTER_REG_W);
/* Single collisions. */
ifp->if_collisions += card_stats & ECR_COLN_MASK;
/* Multiple collisions. */
ifp->if_collisions += (card_stats & ECR_MCOLN_MASK) >> 4;
SMC_SELECT_BANK(sc, 2);
ifp->if_timer = 0;
}
if (sc->sc_chipid == CHIP_91C111 && sc->sc_internal_phy &&
(status & IM_MD_INT)) {
/*
* Internal PHY status change
*/
mii_tick(&sc->sc_mii);
}
/*
* Other errors. Reset the interface.
*/
if (status & IM_EPH_INT) {
smc91cxx_stop(sc);
smc91cxx_init(sc);
}
/*
* Attempt to queue more packets for transmission.
*/
smc91cxx_start(ifp);
out:
/*
* Reenable the interrupts we wish to receive now that processing
* is complete.
*/
mask |= bus_space_read_1(bst, bsh, INTR_MASK_REG_B);
bus_space_write_1(bst, bsh, INTR_MASK_REG_B, mask);
#if NRND > 0
if (status)
rnd_add_uint32(&sc->rnd_source, status);
#endif
return (1);
}
/*
* Read a packet from the card and pass it up to the kernel.
* NOTE! WE EXPECT TO BE IN REGISTER WINDOW 2!
*/
void
smc91cxx_read(sc)
struct smc91cxx_softc *sc;
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
struct ether_header *eh;
struct mbuf *m;
u_int16_t status, packetno, packetlen;
u_int8_t *data;
u_int32_t dr;
again:
/*
* Set data pointer to the beginning of the packet. Since
* PTR_RCV is set, the packet number will be found automatically
* in FIFO_PORTS_REG_W, FIFO_RX_MASK.
*/
bus_space_write_2(bst, bsh, POINTER_REG_W,
PTR_READ | PTR_RCV | PTR_AUTOINC /* | 0x0000 */);
/*
* First two words are status and packet length.
*/
if ((sc->sc_flags & SMC_FLAGS_32BIT_READ) == 0) {
status = bus_space_read_2(bst, bsh, DATA_REG_W);
packetlen = bus_space_read_2(bst, bsh, DATA_REG_W);
} else {
dr = bus_space_read_4(bst, bsh, DATA_REG_W);
#if BYTE_ORDER == LITTLE_ENDIAN
status = (u_int16_t)dr;
packetlen = (u_int16_t)(dr >> 16);
#else
packetlen = (u_int16_t)dr;
status = (u_int16_t)(dr >> 16);
#endif
}
packetlen &= RLEN_MASK;
/*
* The packet length includes 3 extra words: status, length,
* and an extra word that includes the control byte.
*/
packetlen -= 6;
/*
* Account for receive errors and discard.
*/
if (status & RS_ERRORS) {
ifp->if_ierrors++;
goto out;
}
/*
* Adjust for odd-length packet.
*/
if (status & RS_ODDFRAME)
packetlen++;
/*
* Allocate a header mbuf.
*/
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
goto out;
m->m_pkthdr.rcvif = ifp;
m->m_pkthdr.len = packetlen;
/*
* Always put the packet in a cluster.
* XXX should chain small mbufs if less than threshold.
*/
MCLGET(m, M_DONTWAIT);
if ((m->m_flags & M_EXT) == 0) {
m_freem(m);
ifp->if_ierrors++;
printf("%s: can't allocate cluster for incoming packet\n",
sc->sc_dev.dv_xname);
goto out;
}
/*
* Pull the packet off the interface. Make sure the payload
* is aligned.
*/
if ((sc->sc_flags & SMC_FLAGS_32BIT_READ) == 0) {
m->m_data = (caddr_t) ALIGN(mtod(m, caddr_t) +
sizeof(struct ether_header)) - sizeof(struct ether_header);
eh = mtod(m, struct ether_header *);
data = mtod(m, u_int8_t *);
if (packetlen > 1)
bus_space_read_multi_stream_2(bst, bsh, DATA_REG_W,
(u_int16_t *)data, packetlen >> 1);
if (packetlen & 1) {
data += packetlen & ~1;
*data = bus_space_read_1(bst, bsh, DATA_REG_B);
}
} else {
u_int8_t *dp;
m->m_data = (caddr_t) ALIGN(mtod(m, caddr_t));
eh = mtod(m, struct ether_header *);
dp = data = mtod(m, u_int8_t *);
if (packetlen > 3)
bus_space_read_multi_stream_4(bst, bsh, DATA_REG_W,
(u_int32_t *)data, packetlen >> 2);
if (packetlen & 3) {
data += packetlen & ~3;
*((u_int32_t *)data) =
bus_space_read_stream_4(bst, bsh, DATA_REG_W);
}
}
ifp->if_ipackets++;
/*
* Make sure to behave as IFF_SIMPLEX in all cases.
* This is to cope with SMC91C92 (Megahertz XJ10BT), which
* loops back packets to itself on promiscuous mode.
* (should be ensured by chipset configuration)
*/
if ((ifp->if_flags & IFF_PROMISC) != 0) {
/*
* Drop packet looped back from myself.
*/
if (ether_cmp(eh->ether_shost, LLADDR(ifp->if_sadl)) == 0) {
m_freem(m);
goto out;
}
}
m->m_pkthdr.len = m->m_len = packetlen;
#if NBPFILTER > 0
/*
* Hand the packet off to bpf listeners.
*/
if (ifp->if_bpf)
bpf_mtap(ifp->if_bpf, m);
#endif
(*ifp->if_input)(ifp, m);
out:
/*
* Tell the card to free the memory occupied by this packet.
*/
while (bus_space_read_2(bst, bsh, MMU_CMD_REG_W) & MMUCR_BUSY)
/* XXX bound this loop! */ ;
bus_space_write_2(bst, bsh, MMU_CMD_REG_W, MMUCR_RELEASE);
/*
* Check for another packet.
*/
packetno = bus_space_read_2(bst, bsh, FIFO_PORTS_REG_W);
if (packetno & FIFO_REMPTY)
return;
goto again;
}
/*
* Process an ioctl request.
*/
int
smc91cxx_ioctl(ifp, cmd, data)
struct ifnet *ifp;
u_long cmd;
caddr_t data;
{
struct smc91cxx_softc *sc = ifp->if_softc;
struct ifaddr *ifa = (struct ifaddr *)data;
struct ifreq *ifr = (struct ifreq *)data;
int s, error = 0;
s = splnet();
switch (cmd) {
case SIOCSIFADDR:
if ((error = smc91cxx_enable(sc)) != 0)
break;
ifp->if_flags |= IFF_UP;
switch (ifa->ifa_addr->sa_family) {
#ifdef INET
case AF_INET:
smc91cxx_init(sc);
arp_ifinit(ifp, ifa);
break;
#endif
#ifdef NS
case AF_NS:
{
struct ns_addr *ina = &IA_SNS(ifa)->sns_addr;
if (ns_nullhost(*ina))
ina->x_host =
*(union ns_host *)LLADDR(ifp->if_sadl);
else {
memcpy(LLADDR(ifp->if_sadl), ina->x_host.c_host,
ETHER_ADDR_LEN);
}
/*
* Set new address. Reset, because the receiver
* has to be stopped before we can set the new
* MAC address.
*/
smc91cxx_reset(sc);
break;
}
#endif
default:
smc91cxx_init(sc);
break;
}
break;
#if defined(CCITT) && defined(LLC)
case SIOCSIFCONF_X25:
if ((error = smc91cxx_enable(sc)) != 0)
break;
ifp->if_flags |= IFF_UP;
ifa->ifa_rtrequest = cons_rtrequest; /* XXX */
error = x25_llcglue(PRC_IFUP, ifa->ifa_addr);
if (error == 0)
smc91cxx_init(sc);
break;
#endif
case SIOCSIFFLAGS:
if ((ifp->if_flags & IFF_UP) == 0 &&
(ifp->if_flags & IFF_RUNNING) != 0) {
/*
* If interface is marked down and it is running,
* stop it.
*/
smc91cxx_stop(sc);
ifp->if_flags &= ~IFF_RUNNING;
smc91cxx_disable(sc);
} else if ((ifp->if_flags & IFF_UP) != 0 &&
(ifp->if_flags & IFF_RUNNING) == 0) {
/*
* If interface is marked up and it is stopped,
* start it.
*/
if ((error = smc91cxx_enable(sc)) != 0)
break;
smc91cxx_init(sc);
} else if ((ifp->if_flags & IFF_UP) != 0) {
/*
* Reset the interface to pick up changes in any
* other flags that affect hardware registers.
*/
smc91cxx_reset(sc);
}
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
if ((sc->sc_flags & SMC_FLAGS_ENABLED) == 0) {
error = EIO;
break;
}
error = (cmd == SIOCADDMULTI) ?
ether_addmulti(ifr, &sc->sc_ec) :
ether_delmulti(ifr, &sc->sc_ec);
if (error == ENETRESET) {
/*
* Multicast list has changed; set the hardware
* filter accordingly.
*/
if (ifp->if_flags & IFF_RUNNING)
smc91cxx_reset(sc);
error = 0;
}
break;
case SIOCGIFMEDIA:
case SIOCSIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &sc->sc_mii.mii_media, cmd);
break;
default:
error = EINVAL;
break;
}
splx(s);
return (error);
}
/*
* Reset the interface.
*/
void
smc91cxx_reset(sc)
struct smc91cxx_softc *sc;
{
int s;
s = splnet();
smc91cxx_stop(sc);
smc91cxx_init(sc);
splx(s);
}
/*
* Watchdog timer.
*/
void
smc91cxx_watchdog(ifp)
struct ifnet *ifp;
{
struct smc91cxx_softc *sc = ifp->if_softc;
log(LOG_ERR, "%s: device timeout\n", sc->sc_dev.dv_xname);
ifp->if_oerrors++;
smc91cxx_reset(sc);
}
/*
* Stop output on the interface.
*/
void
smc91cxx_stop(sc)
struct smc91cxx_softc *sc;
{
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
/*
* Clear interrupt mask; disable all interrupts.
*/
SMC_SELECT_BANK(sc, 2);
bus_space_write_1(bst, bsh, INTR_MASK_REG_B, 0);
/*
* Disable transmitter and receiver.
*/
SMC_SELECT_BANK(sc, 0);
bus_space_write_2(bst, bsh, RECV_CONTROL_REG_W, 0);
bus_space_write_2(bst, bsh, TXMIT_CONTROL_REG_W, 0);
/*
* Cancel watchdog timer.
*/
sc->sc_ec.ec_if.if_timer = 0;
}
/*
* Enable power on the interface.
*/
int
smc91cxx_enable(sc)
struct smc91cxx_softc *sc;
{
if ((sc->sc_flags & SMC_FLAGS_ENABLED) == 0 && sc->sc_enable != NULL) {
if ((*sc->sc_enable)(sc) != 0) {
printf("%s: device enable failed\n",
sc->sc_dev.dv_xname);
return (EIO);
}
}
sc->sc_flags |= SMC_FLAGS_ENABLED;
return (0);
}
/*
* Disable power on the interface.
*/
void
smc91cxx_disable(sc)
struct smc91cxx_softc *sc;
{
if ((sc->sc_flags & SMC_FLAGS_ENABLED) != 0 && sc->sc_disable != NULL) {
(*sc->sc_disable)(sc);
sc->sc_flags &= ~SMC_FLAGS_ENABLED;
}
}
int
smc91cxx_activate(self, act)
struct device *self;
enum devact act;
{
struct smc91cxx_softc *sc = (struct smc91cxx_softc *)self;
int rv = 0, s;
s = splnet();
switch (act) {
case DVACT_ACTIVATE:
rv = EOPNOTSUPP;
break;
case DVACT_DEACTIVATE:
if_deactivate(&sc->sc_ec.ec_if);
break;
}
splx(s);
return (rv);
}
int
smc91cxx_detach(self, flags)
struct device *self;
int flags;
{
struct smc91cxx_softc *sc = (struct smc91cxx_softc *)self;
struct ifnet *ifp = &sc->sc_ec.ec_if;
/* Succeed now if there's no work to do. */
if ((sc->sc_flags & SMC_FLAGS_ATTACHED) == 0)
return (0);
/* smc91cxx_disable() checks SMC_FLAGS_ENABLED */
smc91cxx_disable(sc);
/* smc91cxx_attach() never fails */
/* Delete all media. */
ifmedia_delete_instance(&sc->sc_mii.mii_media, IFM_INST_ANY);
#if NRND > 0
rnd_detach_source(&sc->rnd_source);
#endif
ether_ifdetach(ifp);
if_detach(ifp);
return (0);
}
u_int32_t
smc91cxx_mii_bitbang_read(self)
struct device *self;
{
struct smc91cxx_softc *sc = (void *) self;
/* We're already in bank 3. */
return (bus_space_read_2(sc->sc_bst, sc->sc_bsh, MGMT_REG_W));
}
void
smc91cxx_mii_bitbang_write(self, val)
struct device *self;
u_int32_t val;
{
struct smc91cxx_softc *sc = (void *) self;
/* We're already in bank 3. */
bus_space_write_2(sc->sc_bst, sc->sc_bsh, MGMT_REG_W, val);
}
int
smc91cxx_mii_readreg(self, phy, reg)
struct device *self;
int phy, reg;
{
struct smc91cxx_softc *sc = (void *) self;
int val;
SMC_SELECT_BANK(sc, 3);
val = mii_bitbang_readreg(self, &smc91cxx_mii_bitbang_ops, phy, reg);
SMC_SELECT_BANK(sc, 2);
return (val);
}
void
smc91cxx_mii_writereg(self, phy, reg, val)
struct device *self;
int phy, reg, val;
{
struct smc91cxx_softc *sc = (void *) self;
SMC_SELECT_BANK(sc, 3);
mii_bitbang_writereg(self, &smc91cxx_mii_bitbang_ops, phy, reg, val);
SMC_SELECT_BANK(sc, 2);
}
void
smc91cxx_statchg(self)
struct device *self;
{
struct smc91cxx_softc *sc = (struct smc91cxx_softc *)self;
bus_space_tag_t bst = sc->sc_bst;
bus_space_handle_t bsh = sc->sc_bsh;
int mctl;
SMC_SELECT_BANK(sc, 0);
mctl = bus_space_read_2(bst, bsh, TXMIT_CONTROL_REG_W);
if (sc->sc_mii.mii_media_active & IFM_FDX)
mctl |= TCR_SWFDUP;
else
mctl &= ~TCR_SWFDUP;
bus_space_write_2(bst, bsh, TXMIT_CONTROL_REG_W, mctl);
SMC_SELECT_BANK(sc, 2); /* back to operating window */
}
/*
* One second timer, used to tick the MII.
*/
void
smc91cxx_tick(arg)
void *arg;
{
struct smc91cxx_softc *sc = arg;
int s;
#ifdef DIAGNOSTIC
if ((sc->sc_flags & SMC_FLAGS_HAS_MII) == 0)
panic("smc91cxx_tick");
#endif
if ((sc->sc_dev.dv_flags & DVF_ACTIVE) == 0)
return;
s = splnet();
mii_tick(&sc->sc_mii);
splx(s);
callout_reset(&sc->sc_mii_callout, hz, smc91cxx_tick, sc);
}