diff options
author | imp <imp@FreeBSD.org> | 2006-02-04 23:32:13 +0000 |
---|---|---|
committer | imp <imp@FreeBSD.org> | 2006-02-04 23:32:13 +0000 |
commit | f2384c93e3e037aeae70e774832e2695f10c6b10 (patch) | |
tree | 0452302e17fa41f14460f74e8e49e382307a52a3 /sys/arm/at91/if_ate.c | |
parent | d83080f6aefe88f9297accf3bbffd91236c3b5f9 (diff) | |
download | FreeBSD-src-f2384c93e3e037aeae70e774832e2695f10c6b10.zip FreeBSD-src-f2384c93e3e037aeae70e774832e2695f10c6b10.tar.gz |
Import support for the Atmel AT91RM9200 CPU/Microcontroller. This SoC
is a ARM920T based CPU with a bunch of built-in peripherals. The
inital import supports the SPI bus, the TWI bus (although iicbus
integration is not complete), the uarts, the system timer and the
onboard ethernet. Support for the Kwikbyte KB9202
(http://www.kwikbyte.com) board is also included, although there's no
reason why the 9200 and the 9201 wouldn't also work. Primitive
support for running under the skyeye emulator is also provided
(although skyeye's support for the AT91RM9200 is a little weak).
The code has been structured so that other members of Atmel's arm family can
be supported in the future. The AT91SAM9260 is not presently supported
due to lack of hardware. The arm7tdmi families are also not supported
becasue they lack an MMU.
Many thanks to cognet@ for his help and assistance in bringing up this
board. He did much of the vm work and wrote parts of the uart and
system timer code as well as the bus space implementation.
The system boots to single user w/o problem, although the serial
console is a little slow and the ethernet driver is still in flux.
This work was sponsored by Timing Solutions, Corporation. I am
grateful to their support of the FreeBSD project in this manner.
Diffstat (limited to 'sys/arm/at91/if_ate.c')
-rw-r--r-- | sys/arm/at91/if_ate.c | 982 |
1 files changed, 982 insertions, 0 deletions
diff --git a/sys/arm/at91/if_ate.c b/sys/arm/at91/if_ate.c new file mode 100644 index 0000000..2736240 --- /dev/null +++ b/sys/arm/at91/if_ate.c @@ -0,0 +1,982 @@ +/*- + * 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); + + 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; + } + + sc->ifp = ifp = if_alloc(IFT_ETHER); + 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; + } + /* + * 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 atestart() 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); |