freebsd-dev/sys/arm/at91/if_ate.c
Olivier Houchard ea9f99ddcd - Call mii_phy_probe() after we allocated an ifp. mii has this evil
hack where it assumes the first field of the driver softc is the struct
ifnet, and it copies its value in mii_phy_probe().
- In the interrupt handler, set the mbuf m_len field on packet receive.
2006-02-06 22:17:42 +00:00

985 lines
25 KiB
C

/*-
* Copyright (c) 2006 M. Warner Losh. 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.
*
* 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.
*/
/* TODO: (in no order)
*
* 5) Setup RX buffers in ateinit_locked
* 8) Need to sync busdma goo in atestop
* 9) atestop should maybe free the mbufs?
* 10) On Rx, how do we get a new mbuf?
*
* 1) detach
* 2) Free dma setup
* 3) Turn on the clock in pmc and turn on pins? Turn off?
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <machine/bus.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_mib.h>
#include <net/if_types.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#endif
#include <net/bpf.h>
#include <net/bpfdesc.h>
#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <arm/at91/if_atereg.h>
#include "miibus_if.h"
#define ATE_MAX_TX_BUFFERS 2 /* We have ping-pong tx buffers */
#define ATE_MAX_RX_BUFFERS 8
struct ate_softc
{
struct ifnet *ifp; /* ifnet pointer */
struct mtx sc_mtx; /* basically a perimeter lock */
device_t dev; /* Myself */
device_t miibus; /* My child miibus */
void *intrhand; /* Interrupt handle */
struct resource *irq_res; /* IRQ resource */
struct resource *mem_res; /* Memory resource */
struct callout tick_ch; /* Tick callout */
bus_dma_tag_t mtag; /* bus dma tag for mbufs */
bus_dmamap_t tx_map[ATE_MAX_TX_BUFFERS];
bus_dma_tag_t rxtag;
bus_dmamap_t rx_map[ATE_MAX_RX_BUFFERS];
bus_dma_tag_t rx_desc_tag;
bus_dmamap_t rx_desc_map;
int txcur; /* current tx map pointer */
struct mbuf *sent_mbuf[ATE_MAX_TX_BUFFERS]; /* Sent mbufs */
struct mbuf *rx_mbuf[ATE_MAX_RX_BUFFERS]; /* RX mbufs */
bus_addr_t rx_desc_phys;
eth_rx_desc_t *rx_descs;
struct ifmib_iso_8802_3 mibdata; /* stuff for network mgmt */
};
static inline uint32_t
RD4(struct ate_softc *sc, bus_size_t off)
{
return bus_read_4(sc->mem_res, off);
}
static inline void
WR4(struct ate_softc *sc, bus_size_t off, uint32_t val)
{
bus_write_4(sc->mem_res, off, val);
}
#define ATE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
#define ATE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
#define ATE_LOCK_INIT(_sc) \
mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
MTX_NETWORK_LOCK, MTX_DEF)
#define ATE_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx);
#define ATE_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED);
#define ATE_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
static devclass_t ate_devclass;
/* ifnet entry points */
static void ateinit_locked(void *);
static void atestart_locked(struct ifnet *);
static void ateinit(void *);
static void atestart(struct ifnet *);
static void atestop(struct ate_softc *);
static void atewatchdog(struct ifnet *);
static int ateioctl(struct ifnet * ifp, u_long, caddr_t);
/* bus entry points */
static int ate_probe(device_t dev);
static int ate_attach(device_t dev);
static int ate_detach(device_t dev);
static void ate_intr(void *);
/* helper routines */
static int ate_activate(device_t dev);
static void ate_deactivate(device_t dev);
static int ate_ifmedia_upd(struct ifnet *ifp);
static void ate_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr);
static void ate_get_mac(struct ate_softc *sc, u_char *eaddr);
/*
* The AT91 family of products has the ethernet called EMAC. However,
* it isn't self identifying. It is anticipated that the parent bus
* code will take care to only add ate devices where they really are. As
* such, we do nothing here to identify the device and just set its name.
*/
static int
ate_probe(device_t dev)
{
device_set_desc(dev, "EMAC");
return (0);
}
static int
ate_attach(device_t dev)
{
struct ate_softc *sc = device_get_softc(dev);
struct ifnet *ifp = NULL;
int err;
u_char eaddr[6];
sc->dev = dev;
err = ate_activate(dev);
if (err)
goto out;
/* calling atestop before ifp is set is OK */
atestop(sc);
ATE_LOCK_INIT(sc);
callout_init_mtx(&sc->tick_ch, &sc->sc_mtx, 0);
ate_get_mac(sc, eaddr);
sc->ifp = ifp = if_alloc(IFT_ETHER);
if (mii_phy_probe(dev, &sc->miibus, ate_ifmedia_upd, ate_ifmedia_sts)) {
device_printf(dev, "Cannot find my PHY.\n");
err = ENXIO;
goto out;
}
ifp->if_softc = sc;
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
ifp->if_mtu = ETHERMTU;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_start = atestart;
ifp->if_ioctl = ateioctl;
ifp->if_watchdog = atewatchdog;
ifp->if_init = ateinit;
ifp->if_baudrate = 10000000;
IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
ifp->if_snd.ifq_maxlen = IFQ_MAXLEN;
IFQ_SET_READY(&ifp->if_snd);
ifp->if_timer = 0;
ifp->if_linkmib = &sc->mibdata;
ifp->if_linkmiblen = sizeof(sc->mibdata);
sc->mibdata.dot3Compliance = DOT3COMPLIANCE_COLLS;
ether_ifattach(ifp, eaddr);
/*
* Activate the interrupt
*/
err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET | INTR_MPSAFE,
ate_intr, sc, &sc->intrhand);
if (err) {
ether_ifdetach(ifp);
ATE_LOCK_DESTROY(sc);
}
out:;
if (err)
ate_deactivate(dev);
if (err && ifp)
if_free(ifp);
return (err);
}
static int
ate_detach(device_t dev)
{
return EBUSY; /* XXX TODO(1) */
}
static void
ate_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
{
struct ate_softc *sc;
if (error != 0)
return;
sc = (struct ate_softc *)arg;
sc->rx_desc_phys = segs[0].ds_addr;
}
/*
* Compute the multicast filter for this device using the standard
* algorithm. I wonder why this isn't in ether somewhere as a lot
* of different MAC chips use this method (or the reverse the bits)
* method.
*/
static void
ate_setmcast(struct ate_softc *sc)
{
uint32_t index;
uint32_t mcaf[2];
u_char *af = (u_char *) mcaf;
struct ifmultiaddr *ifma;
mcaf[0] = 0;
mcaf[1] = 0;
IF_ADDR_LOCK(sc->ifp);
TAILQ_FOREACH(ifma, &sc->ifp->if_multiaddrs, ifma_link) {
if (ifma->ifma_addr->sa_family != AF_LINK)
continue;
index = ether_crc32_be(LLADDR((struct sockaddr_dl *)
ifma->ifma_addr), ETHER_ADDR_LEN) >> 26;
af[index >> 3] |= 1 << (index & 7);
}
IF_ADDR_UNLOCK(sc->ifp);
/*
* Write the hash to the hash register. This card can also
* accept unicast packets as well as multicast packets using this
* register for easier bridging operations, but we don't take
* advantage of that. Locks here are to avoid LOR with the
* IF_ADDR_LOCK, but might not be strictly necessary.
*/
ATE_LOCK(sc);
WR4(sc, ETH_HSL, mcaf[0]);
WR4(sc, ETH_HSH, mcaf[1]);
ATE_UNLOCK(sc);
}
static int
ate_activate(device_t dev)
{
struct ate_softc *sc;
int rid, err, i;
sc = device_get_softc(dev);
rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (sc->mem_res == NULL)
goto errout;
rid = 0;
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
RF_ACTIVE);
if (sc->mem_res == NULL)
goto errout;
/*
* Allocate DMA tags and maps
*/
err = bus_dma_tag_create(NULL, 1, 0, BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, MCLBYTES, 0,
busdma_lock_mutex, &sc->sc_mtx, &sc->mtag);
if (err != 0)
goto errout;
for (i = 0; i < ATE_MAX_TX_BUFFERS; i++) {
err = bus_dmamap_create(sc->mtag, 0, &sc->tx_map[i]);
if (err != 0)
goto errout;
}
/*
* Allocate our Rx buffers. This chip has a rx structure that's filled
* in
*/
/*
* Allocate DMA tags and maps for RX.
*/
err = bus_dma_tag_create(NULL, 1, 0, BUS_SPACE_MAXADDR_32BIT,
BUS_SPACE_MAXADDR, NULL, NULL, MCLBYTES, 1, MCLBYTES, 0,
busdma_lock_mutex, &sc->sc_mtx, &sc->rxtag);
if (err != 0)
goto errout;
for (i = 0; i < ATE_MAX_RX_BUFFERS; i++) {
err = bus_dmamap_create(sc->rxtag, 0, &sc->rx_map[i]);
if (err != 0)
goto errout;
}
/* Dma TAG and MAP for the rx descriptors. */
err = bus_dma_tag_create(NULL, sizeof(eth_rx_desc_t), 0,
BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL,
ATE_MAX_RX_BUFFERS * sizeof(eth_rx_desc_t), 1,
ATE_MAX_RX_BUFFERS * sizeof(eth_rx_desc_t), 0, busdma_lock_mutex,
&sc->sc_mtx, &sc->rx_desc_tag);
if (err != 0)
goto errout;
if (bus_dmamem_alloc(sc->rx_desc_tag, (void **)&sc->rx_descs, M_WAITOK,
&sc->rx_desc_map) != 0)
goto errout;
if (bus_dmamap_load(sc->rx_desc_tag, sc->rx_desc_map,
sc->rx_descs, ATE_MAX_RX_BUFFERS * sizeof(eth_rx_desc_t),
ate_getaddr, sc, 0) != 0)
goto errout;
/* XXX TODO(5) Put this in ateinit_locked? */
for (i = 0; i < ATE_MAX_RX_BUFFERS; i++) {
bus_dma_segment_t seg;
int nsegs;
sc->rx_mbuf[i] = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);
sc->rx_mbuf[i]->m_len = sc->rx_mbuf[i]->m_pkthdr.len =
MCLBYTES;
if (bus_dmamap_load_mbuf_sg(sc->rxtag, sc->rx_map[i],
sc->rx_mbuf[i], &seg, &nsegs, 0) != 0)
goto errout;
/*
* For the last buffer, set the wrap bit so the controller
* restarts from the first descriptor.
*/
if (i == ATE_MAX_RX_BUFFERS - 1)
seg.ds_addr |= 1 << 1;
sc->rx_descs[i].addr = seg.ds_addr;
sc->rx_descs[i].status = 0;
bus_dmamap_sync(sc->rxtag, sc->rx_map[i], BUS_DMASYNC_PREWRITE);
}
bus_dmamap_sync(sc->rx_desc_tag, sc->rx_desc_map, BUS_DMASYNC_PREWRITE);
/* Write the descriptor queue address. */
WR4(sc, ETH_RBQP, sc->rx_desc_phys);
return (0);
errout:
ate_deactivate(dev);
return (ENOMEM);
}
static void
ate_deactivate(device_t dev)
{
struct ate_softc *sc;
sc = device_get_softc(dev);
/* XXX TODO(2) teardown busdma junk, below from fxp -- customize */
#if 0
if (sc->fxp_mtag) {
for (i = 0; i < FXP_NRFABUFS; i++) {
rxp = &sc->fxp_desc.rx_list[i];
if (rxp->rx_mbuf != NULL) {
bus_dmamap_sync(sc->fxp_mtag, rxp->rx_map,
BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->fxp_mtag, rxp->rx_map);
m_freem(rxp->rx_mbuf);
}
bus_dmamap_destroy(sc->fxp_mtag, rxp->rx_map);
}
bus_dmamap_destroy(sc->fxp_mtag, sc->spare_map);
for (i = 0; i < FXP_NTXCB; i++) {
txp = &sc->fxp_desc.tx_list[i];
if (txp->tx_mbuf != NULL) {
bus_dmamap_sync(sc->fxp_mtag, txp->tx_map,
BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(sc->fxp_mtag, txp->tx_map);
m_freem(txp->tx_mbuf);
}
bus_dmamap_destroy(sc->fxp_mtag, txp->tx_map);
}
bus_dma_tag_destroy(sc->fxp_mtag);
}
if (sc->fxp_stag)
bus_dma_tag_destroy(sc->fxp_stag);
if (sc->cbl_tag)
bus_dma_tag_destroy(sc->cbl_tag);
if (sc->mcs_tag)
bus_dma_tag_destroy(sc->mcs_tag);
#endif
if (sc->intrhand)
bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
sc->intrhand = 0;
bus_generic_detach(sc->dev);
if (sc->miibus)
device_delete_child(sc->dev, sc->miibus);
if (sc->mem_res)
bus_release_resource(dev, SYS_RES_IOPORT,
rman_get_rid(sc->mem_res), sc->mem_res);
sc->mem_res = 0;
if (sc->irq_res)
bus_release_resource(dev, SYS_RES_IRQ,
rman_get_rid(sc->irq_res), sc->irq_res);
sc->irq_res = 0;
return;
}
/*
* Change media according to request.
*/
static int
ate_ifmedia_upd(struct ifnet *ifp)
{
struct ate_softc *sc = ifp->if_softc;
struct mii_data *mii;
mii = device_get_softc(sc->miibus);
ATE_LOCK(sc);
mii_mediachg(mii);
ATE_UNLOCK(sc);
return (0);
}
/*
* Notify the world which media we're using.
*/
static void
ate_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
{
struct ate_softc *sc = ifp->if_softc;
struct mii_data *mii;
mii = device_get_softc(sc->miibus);
ATE_LOCK(sc);
mii_pollstat(mii);
ifmr->ifm_active = mii->mii_media_active;
ifmr->ifm_status = mii->mii_media_status;
ATE_UNLOCK(sc);
}
static void
ate_tick(void *xsc)
{
struct ate_softc *sc = xsc;
struct mii_data *mii;
int active;
/*
* The KB920x boot loader tests ETH_SR & ETH_SR_LINK and will ask
* the MII if there's a link if this bit is clear. Not sure if we
* should do the same thing here or not.
*/
ATE_ASSERT_LOCKED(sc);
if (sc->miibus != NULL) {
mii = device_get_softc(sc->miibus);
active = mii->mii_media_active;
mii_tick(mii);
if (mii->mii_media_status & IFM_ACTIVE &&
active != mii->mii_media_active) {
/*
* The speed and full/half-duplex state needs
* to be reflected in the ETH_CFG register, it
* seems.
*/
if (IFM_SUBTYPE(mii->mii_media_active) == IFM_10_T)
WR4(sc, ETH_CFG, RD4(sc, ETH_CFG) &
~ETH_CFG_SPD);
else
WR4(sc, ETH_CFG, RD4(sc, ETH_CFG) |
ETH_CFG_SPD);
if (mii->mii_media_active & IFM_FDX)
WR4(sc, ETH_CFG, RD4(sc, ETH_CFG) |
ETH_CFG_FD);
else
WR4(sc, ETH_CFG, RD4(sc, ETH_CFG) &
~ETH_CFG_FD);
}
}
/*
* Update the stats as best we can. When we're done, clear
* the status counters and start over. We're supposed to read these
* registers often enough that they won't overflow. Hopefully
* once a second is often enough. Some don't map well to
* the dot3Stats mib, so for those we just count them as general
* errors. Stats for iframes, ibutes, oframes and obytes are
* collected elsewhere. These registers zero on a read to prevent
* races.
*/
sc->mibdata.dot3StatsAlignmentErrors += RD4(sc, ETH_ALE);
sc->mibdata.dot3StatsFCSErrors += RD4(sc, ETH_SEQE);
sc->mibdata.dot3StatsSingleCollisionFrames += RD4(sc, ETH_SCOL);
sc->mibdata.dot3StatsMultipleCollisionFrames += RD4(sc, ETH_MCOL);
sc->mibdata.dot3StatsSQETestErrors += RD4(sc, ETH_SQEE);
sc->mibdata.dot3StatsDeferredTransmissions += RD4(sc, ETH_DTE);
sc->mibdata.dot3StatsLateCollisions += RD4(sc, ETH_LCOL);
sc->mibdata.dot3StatsExcessiveCollisions += RD4(sc, ETH_ECOL);
sc->mibdata.dot3StatsCarrierSenseErrors += RD4(sc, ETH_CSE);
sc->mibdata.dot3StatsFrameTooLongs += RD4(sc, ETH_ELR);
sc->mibdata.dot3StatsInternalMacReceiveErrors += RD4(sc, ETH_DRFC);
/*
* not sure where to lump these, so count them against the errors
* for the interface.
*/
sc->ifp->if_oerrors += RD4(sc, ETH_CSE) + RD4(sc, ETH_TUE);
sc->ifp->if_ierrors += RD4(sc, ETH_CDE) + RD4(sc, ETH_RJB) +
RD4(sc, ETH_USF);
/*
* Schedule another timeout one second from now.
*/
callout_reset(&sc->tick_ch, hz, ate_tick, sc);
}
static void
ate_get_mac(struct ate_softc *sc, u_char *eaddr)
{
uint32_t low, high;
/*
* The KB920x loaders will setup the MAC with an address, if one
* is set in the loader. The TSC loader will also set the MAC address
* in a similar way. Grab the MAC address from the SA1[HL] registers.
*/
low = RD4(sc, ETH_SA1L);
high = RD4(sc, ETH_SA1H);
eaddr[0] = (high >> 8) & 0xff;
eaddr[1] = high & 0xff;
eaddr[2] = (low >> 24) & 0xff;
eaddr[3] = (low >> 16) & 0xff;
eaddr[4] = (low >> 8) & 0xff;
eaddr[5] = low & 0xff;
}
static void
ate_intr(void *xsc)
{
struct ate_softc *sc = xsc;
int status;
int i;
status = RD4(sc, ETH_ISR);
if (status == 0)
return;
printf("IT IS %x\n", RD4(sc, ETH_RSR));
if (status & ETH_ISR_RCOM) {
bus_dmamap_sync(sc->rx_desc_tag, sc->rx_desc_map,
BUS_DMASYNC_POSTREAD);
for (i = 0; i < ATE_MAX_RX_BUFFERS; i++) {
if (sc->rx_descs[i].addr & ETH_CPU_OWNER) {
struct mbuf *mb = sc->rx_mbuf[i];
bus_dma_segment_t seg;
int rx_stat = sc->rx_descs[i].status;
int nsegs;
printf("GOT ONE\n");
bus_dmamap_sync(sc->rxtag,
sc->rx_map[i], BUS_DMASYNC_POSTREAD);
bus_dmamap_unload(sc->rxtag,
sc->rx_map[i]);
WR4(sc, ETH_RSR, RD4(sc, ETH_RSR));
/*
* Allocate a new buffer to replace this one.
* if we cannot, then we drop this packet
* and keep the old buffer we had.
*/
sc->rx_mbuf[i] = m_getcl(M_DONTWAIT, MT_DATA,
M_PKTHDR);
if (!sc->rx_mbuf[i]) {
sc->rx_mbuf[i] = mb;
sc->rx_descs[i].addr &= ~ETH_CPU_OWNER;
bus_dmamap_sync(sc->rx_desc_tag,
sc->rx_desc_map,
BUS_DMASYNC_PREWRITE);
continue;
}
if (bus_dmamap_load_mbuf_sg(sc->rxtag,
sc->rx_map[i],
sc->rx_mbuf[i], &seg, &nsegs, 0) != 0) {
sc->rx_mbuf[i] = mb;
sc->rx_descs[i].addr &= ~ETH_CPU_OWNER;
bus_dmamap_sync(sc->rx_desc_tag,
sc->rx_desc_map,
BUS_DMASYNC_PREWRITE);
continue;
}
mb->m_len = sc->rx_descs[i].status &
ETH_LEN_MASK;
/*
* For the last buffer, set the wrap bit so
* the controller restarts from the first
* descriptor.
*/
if (i == ATE_MAX_RX_BUFFERS - 1)
seg.ds_addr |= 1 << 1;
sc->rx_descs[i].addr = seg.ds_addr;
sc->rx_descs[i].status = 0;
mb->m_len = rx_stat & ETH_LEN_MASK;
(*sc->ifp->if_input)(sc->ifp, mb);
break;
}
}
}
if (status & ETH_ISR_TCOM) {
if (sc->sent_mbuf[0])
m_freem(sc->sent_mbuf[0]);
if (sc->sent_mbuf[1]) {
if (RD4(sc, ETH_TSR) & ETH_TSR_IDLE) {
m_freem(sc->sent_mbuf[1]);
sc->txcur = 0;
sc->sent_mbuf[0] = sc->sent_mbuf[1] = NULL;
} else {
sc->sent_mbuf[0] = sc->sent_mbuf[1];
sc->sent_mbuf[1] = NULL;
sc->txcur = 1;
}
} else {
sc->sent_mbuf[0] = NULL;
sc->txcur = 0;
}
}
if (status & ETH_ISR_RBNA) {
/* Workaround Errata #11 */
WR4(sc, ETH_CTL, RD4(sc, ETH_CTL) &~ ETH_CTL_RE);
WR4(sc, ETH_CTL, RD4(sc, ETH_CTL) | ETH_CTL_RE);
}
}
/*
* Reset and initialize the chip
*/
static void
ateinit_locked(void *xsc)
{
struct ate_softc *sc = xsc;
struct ifnet *ifp = sc->ifp;
ATE_ASSERT_LOCKED(sc);
/*
* XXX TODO(3)
* we need to turn on the EMAC clock in the pmc. With the
* default boot loader, this is already turned on. However, we
* need to think about how best to turn it on/off as the interface
* is brought up/down, as well as dealing with the mii bus...
*
* We also need to multiplex the pins correctly.
*/
/*
* There are two different ways that the mii bus is connected
* to this chip. Select the right one based on a compile-time
* option.
*/
#ifdef ATE_USE_RMII
WR4(sc, ETH_CFG, RD4(sc, ETH_CFG) | ETH_CFG_RMII);
#else
WR4(sc, ETH_CFG, RD4(sc, ETH_CFG) & ~ETH_CFG_RMII);
#endif
/*
* Turn on the multicast hash, and write 0's to it.
*/
WR4(sc, ETH_CFG, RD4(sc, ETH_CFG) | ETH_CFG_MTI);
WR4(sc, ETH_HSH, 0);
WR4(sc, ETH_HSL, 0);
WR4(sc, ETH_CTL, RD4(sc, ETH_CTL) | ETH_CTL_TE | ETH_CTL_RE);
WR4(sc, ETH_IER, /*ETH_ISR_RCOM | ETH_ISR_TCOM | ETH_ISR_RBNA*/
0xffffffff);
/*
* Boot loader fills in MAC address. If that's not the case, then
* we should set SA1L and SA1H here to the appropriate value. Note:
* the byte order is big endian, not little endian, so we have some
* swapping to do. Again, if we need it (which I don't think we do).
*/
ate_setmcast(sc);
/*
* Set 'running' flag, and clear output active flag
* and attempt to start the output
*/
ifp->if_drv_flags |= IFF_DRV_RUNNING;
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
atestart_locked(ifp);
callout_reset(&sc->tick_ch, hz, ate_tick, sc);
}
/*
* dequeu packets and transmit
*/
static void
atestart_locked(struct ifnet *ifp)
{
struct ate_softc *sc = ifp->if_softc;
struct mbuf *m, *mdefrag;
bus_dma_segment_t segs[1];
int nseg;
ATE_ASSERT_LOCKED(sc);
if (ifp->if_drv_flags & IFF_DRV_OACTIVE)
return;
outloop:
/*
* check to see if there's room to put another packet into the
* xmit queue. The EMAC chip has a ping-pong buffer for xmit
* packets. We use OACTIVE to indicate "we can stuff more into
* our buffers (clear) or not (set)."
*/
if (!(RD4(sc, ETH_TSR) & ETH_TSR_BNQ)) {
ifp->if_drv_flags |= IFF_DRV_OACTIVE;
return;
}
IFQ_DRV_DEQUEUE(&ifp->if_snd, m);
if (m == 0) {
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
return;
}
mdefrag = m_defrag(m, M_DONTWAIT);
if (mdefrag == NULL) {
m_freem(m);
return;
}
m = mdefrag;
if (bus_dmamap_load_mbuf_sg(sc->mtag, sc->tx_map[sc->txcur], m, segs,
&nseg, 0) != 0) {
m_free(m);
goto outloop;
}
bus_dmamap_sync(sc->mtag, sc->tx_map[sc->txcur], BUS_DMASYNC_PREWRITE);
sc->sent_mbuf[sc->txcur] = m;
sc->txcur++;
if (sc->txcur >= ATE_MAX_TX_BUFFERS)
sc->txcur = 0;
/*
* tell the hardware to xmit the packet.
*/
WR4(sc, ETH_TAR, segs[0].ds_addr);
WR4(sc, ETH_TCR, segs[0].ds_len);
/*
* Tap off here if there is a bpf listener.
*/
BPF_MTAP(ifp, m);
/*
* Once we've queued one packet, we'll do the rest via the ISR,
* save off a pointer.
*/
sc->sent_mbuf[1] = m;
}
static void
ateinit(void *xsc)
{
struct ate_softc *sc = xsc;
ATE_LOCK(sc);
ateinit_locked(sc);
ATE_UNLOCK(sc);
}
static void
atestart(struct ifnet *ifp)
{
struct ate_softc *sc = ifp->if_softc;
ATE_LOCK(sc);
atestart_locked(ifp);
ATE_UNLOCK(sc);
}
/*
* Turn off interrupts, and stop the nic. Can be called with sc->ifp NULL
* so be careful.
*/
static void
atestop(struct ate_softc *sc)
{
struct ifnet *ifp = sc->ifp;
if (ifp) {
ifp->if_timer = 0;
ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
}
callout_stop(&sc->tick_ch);
/*
* Enable some parts of the MAC that are needed always (like the
* MII bus. This turns off the RE and TE bits, which will remain
* off until ateinit() is called to turn them on. With RE and TE
* turned off, there's no DMA to worry about after this write.
*/
WR4(sc, ETH_CTL, ETH_CTL_MPE);
/*
* Turn off all the configured options and revert to defaults.
*/
WR4(sc, ETH_CFG, ETH_CFG_CLK_32);
/*
* Turn off all the interrupts, and ack any pending ones by reading
* the ISR.
*/
WR4(sc, ETH_IDR, 0xffffffff);
RD4(sc, ETH_ISR);
/*
* Clear out the Transmit and Receiver Status registers of any
* errors they may be reporting
*/
WR4(sc, ETH_TSR, 0xffffffff);
WR4(sc, ETH_RSR, 0xffffffff);
/*
* XXX TODO(8)
* need to worry about the busdma resources? Yes, I think we need
* to sync and unload them. We may also need to release the mbufs
* that are assocaited with RX and TX operations.
*/
/*
* XXX we should power down the EMAC if it isn't in use, after
* putting it into loopback mode. This saves about 400uA according
* to the datasheet.
*/
}
static void
atewatchdog(struct ifnet *ifp)
{
struct ate_softc *sc = ifp->if_softc;
ATE_LOCK(sc);
device_printf(sc->dev, "Device timeout\n");
ifp->if_oerrors++;
ateinit_locked(sc);
ATE_UNLOCK(sc);
}
static int
ateioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct ate_softc *sc = ifp->if_softc;
int error = 0;
switch (cmd) {
case SIOCSIFFLAGS:
ATE_LOCK(sc);
if ((ifp->if_flags & IFF_UP) == 0 &&
ifp->if_drv_flags & IFF_DRV_RUNNING) {
ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
atestop(sc);
} else {
/* reinitialize card on any parameter change */
ateinit_locked(sc);
}
ATE_UNLOCK(sc);
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
/* update multicast filter list. */
ate_setmcast(sc);
error = 0;
break;
default:
error = ether_ioctl(ifp, cmd, data);
break;
}
return (error);
}
static void
ate_child_detached(device_t dev, device_t child)
{
struct ate_softc *sc;
sc = device_get_softc(dev);
if (child == sc->miibus)
sc->miibus = NULL;
}
/*
* MII bus support routines.
*/
static int
ate_miibus_readreg(device_t dev, int phy, int reg)
{
struct ate_softc *sc;
int val;
/*
* XXX if we implement agressive power savings, then we need
* XXX to make sure that the clock to the emac is on here
*/
if (phy != 0)
return (0xffff);
sc = device_get_softc(dev);
DELAY(1); /* Hangs w/o this delay really 30.5us atm */
WR4(sc, ETH_MAN, ETH_MAN_REG_RD(phy, reg));
while ((RD4(sc, ETH_SR) & ETH_SR_IDLE) == 0)
continue;
val = RD4(sc, ETH_MAN) & ETH_MAN_VALUE_MASK;
return (val);
}
static void
ate_miibus_writereg(device_t dev, int phy, int reg, int data)
{
struct ate_softc *sc;
/*
* XXX if we implement agressive power savings, then we need
* XXX to make sure that the clock to the emac is on here
*/
sc = device_get_softc(dev);
WR4(sc, ETH_MAN, ETH_MAN_REG_WR(phy, reg, data));
while ((RD4(sc, ETH_SR) & ETH_SR_IDLE) == 0)
continue;
return;
}
static device_method_t ate_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ate_probe),
DEVMETHOD(device_attach, ate_attach),
DEVMETHOD(device_detach, ate_detach),
/* Bus interface */
DEVMETHOD(bus_child_detached, ate_child_detached),
/* MII interface */
DEVMETHOD(miibus_readreg, ate_miibus_readreg),
DEVMETHOD(miibus_writereg, ate_miibus_writereg),
{ 0, 0 }
};
static driver_t ate_driver = {
"ate",
ate_methods,
sizeof(struct ate_softc),
};
DRIVER_MODULE(ate, atmelarm, ate_driver, ate_devclass, 0, 0);
DRIVER_MODULE(miibus, ate, miibus_driver, miibus_devclass, 0, 0);
MODULE_DEPEND(ate, miibus, 1, 1, 1);
MODULE_DEPEND(ate, ether, 1, 1, 1);