svn commit: r299084 - head/sys/arm/allwinner
Jared McNeill
jmcneill at FreeBSD.org
Wed May 4 20:06:22 UTC 2016
Author: jmcneill
Date: Wed May 4 20:06:20 2016
New Revision: 299084
URL: https://svnweb.freebsd.org/changeset/base/299084
Log:
Add driver for Allwinner A83T/H3/A64 Gigabit Ethernet.
The datasheets refer to this controller as EMAC, not to be confused with
the fast ethernet controller (also named EMAC) found in A10/A20 SoCs.
Tested on a BananaPi M3 (A83T), which uses an external RGMII PHY (RTL8211E).
Reviewed by: adrian
Differential Revision: https://reviews.freebsd.org/D6169
Added:
head/sys/arm/allwinner/if_awg.c (contents, props changed)
head/sys/arm/allwinner/if_awgreg.h (contents, props changed)
Modified:
head/sys/arm/allwinner/files.allwinner
Modified: head/sys/arm/allwinner/files.allwinner
==============================================================================
--- head/sys/arm/allwinner/files.allwinner Wed May 4 18:08:38 2016 (r299083)
+++ head/sys/arm/allwinner/files.allwinner Wed May 4 20:06:20 2016 (r299084)
@@ -18,6 +18,7 @@ arm/allwinner/a20/a20_cpu_cfg.c standar
arm/allwinner/allwinner_machdep.c standard
arm/allwinner/aw_mp.c optional smp
arm/allwinner/axp209.c optional axp209
+arm/allwinner/if_awg.c optional awg
arm/allwinner/if_emac.c optional emac
arm/allwinner/sunxi_dma_if.m standard
dev/iicbus/twsi/a10_twsi.c optional twsi
Added: head/sys/arm/allwinner/if_awg.c
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ head/sys/arm/allwinner/if_awg.c Wed May 4 20:06:20 2016 (r299084)
@@ -0,0 +1,1418 @@
+/*-
+ * Copyright (c) 2016 Jared McNeill <jmcneill at invisible.ca>
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ * Allwinner Gigabit Ethernet MAC (EMAC) controller
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/kernel.h>
+#include <sys/endian.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/module.h>
+#include <sys/taskqueue.h>
+
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+#include <net/if_var.h>
+
+#include <machine/bus.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <arm/allwinner/if_awgreg.h>
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/hwreset/hwreset.h>
+#include <dev/extres/regulator/regulator.h>
+
+#include "miibus_if.h"
+
+#define RD4(sc, reg) bus_read_4((sc)->res[0], (reg))
+#define WR4(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val))
+
+#define AWG_LOCK(sc) mtx_lock(&(sc)->mtx)
+#define AWG_UNLOCK(sc) mtx_unlock(&(sc)->mtx);
+#define AWG_ASSERT_LOCKED(sc) mtx_assert(&(sc)->mtx, MA_OWNED)
+#define AWG_ASSERT_UNLOCKED(sc) mtx_assert(&(sc)->mtx, MA_NOTOWNED)
+
+#define DESC_ALIGN 4
+#define TX_DESC_COUNT 256
+#define TX_DESC_SIZE (sizeof(struct emac_desc) * TX_DESC_COUNT)
+#define RX_DESC_COUNT 256
+#define RX_DESC_SIZE (sizeof(struct emac_desc) * RX_DESC_COUNT)
+
+#define DESC_OFF(n) ((n) * sizeof(struct emac_desc))
+#define TX_NEXT(n) (((n) + 1) & (TX_DESC_COUNT - 1))
+#define TX_SKIP(n, o) (((n) + (o)) & (TX_DESC_COUNT - 1))
+#define RX_NEXT(n) (((n) + 1) & (RX_DESC_COUNT - 1))
+
+#define TX_MAX_SEGS 10
+
+#define SOFT_RST_RETRY 1000
+#define MII_BUSY_RETRY 1000
+#define MDIO_FREQ 2500000
+
+#define BURST_LEN_DEFAULT 8
+#define RX_TX_PRI_DEFAULT 0
+#define PAUSE_TIME_DEFAULT 0x400
+#define TX_INTERVAL_DEFAULT 64
+
+/* Burst length of RX and TX DMA transfers */
+static int awg_burst_len = BURST_LEN_DEFAULT;
+TUNABLE_INT("hw.awg.burst_len", &awg_burst_len);
+
+/* RX / TX DMA priority. If 1, RX DMA has priority over TX DMA. */
+static int awg_rx_tx_pri = RX_TX_PRI_DEFAULT;
+TUNABLE_INT("hw.awg.rx_tx_pri", &awg_rx_tx_pri);
+
+/* Pause time field in the transmitted control frame */
+static int awg_pause_time = PAUSE_TIME_DEFAULT;
+TUNABLE_INT("hw.awg.pause_time", &awg_pause_time);
+
+/* Request a TX interrupt every <n> descriptors */
+static int awg_tx_interval = TX_INTERVAL_DEFAULT;
+TUNABLE_INT("hw.awg.tx_interval", &awg_tx_interval);
+
+static struct ofw_compat_data compat_data[] = {
+ { "allwinner,sun8i-a83t-emac", 1 },
+ { NULL, 0 }
+};
+
+struct awg_bufmap {
+ bus_dmamap_t map;
+ struct mbuf *mbuf;
+};
+
+struct awg_txring {
+ bus_dma_tag_t desc_tag;
+ bus_dmamap_t desc_map;
+ struct emac_desc *desc_ring;
+ bus_addr_t desc_ring_paddr;
+ bus_dma_tag_t buf_tag;
+ struct awg_bufmap buf_map[TX_DESC_COUNT];
+ u_int cur, next, queued;
+};
+
+struct awg_rxring {
+ bus_dma_tag_t desc_tag;
+ bus_dmamap_t desc_map;
+ struct emac_desc *desc_ring;
+ bus_addr_t desc_ring_paddr;
+ bus_dma_tag_t buf_tag;
+ struct awg_bufmap buf_map[RX_DESC_COUNT];
+ u_int cur;
+};
+
+struct awg_softc {
+ struct resource *res[2];
+ struct mtx mtx;
+ if_t ifp;
+ device_t miibus;
+ struct callout stat_ch;
+ struct task link_task;
+ void *ih;
+ u_int mdc_div_ratio_m;
+ int link;
+ int if_flags;
+
+ struct awg_txring tx;
+ struct awg_rxring rx;
+};
+
+static struct resource_spec awg_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE },
+ { -1, 0 }
+};
+
+static int
+awg_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct awg_softc *sc;
+ int retry, val;
+
+ sc = device_get_softc(dev);
+ val = 0;
+
+ WR4(sc, EMAC_MII_CMD,
+ (sc->mdc_div_ratio_m << MDC_DIV_RATIO_M_SHIFT) |
+ (phy << PHY_ADDR_SHIFT) |
+ (reg << PHY_REG_ADDR_SHIFT) |
+ MII_BUSY);
+ for (retry = MII_BUSY_RETRY; retry > 0; retry--) {
+ if ((RD4(sc, EMAC_MII_CMD) & MII_BUSY) == 0) {
+ val = RD4(sc, EMAC_MII_DATA);
+ break;
+ }
+ DELAY(10);
+ }
+
+ if (retry == 0)
+ device_printf(dev, "phy read timeout, phy=%d reg=%d\n",
+ phy, reg);
+
+ return (val);
+}
+
+static int
+awg_miibus_writereg(device_t dev, int phy, int reg, int val)
+{
+ struct awg_softc *sc;
+ int retry;
+
+ sc = device_get_softc(dev);
+
+ WR4(sc, EMAC_MII_DATA, val);
+ WR4(sc, EMAC_MII_CMD,
+ (sc->mdc_div_ratio_m << MDC_DIV_RATIO_M_SHIFT) |
+ (phy << PHY_ADDR_SHIFT) |
+ (reg << PHY_REG_ADDR_SHIFT) |
+ MII_WR | MII_BUSY);
+ for (retry = MII_BUSY_RETRY; retry > 0; retry--) {
+ if ((RD4(sc, EMAC_MII_CMD) & MII_BUSY) == 0)
+ break;
+ DELAY(10);
+ }
+
+ if (retry == 0)
+ device_printf(dev, "phy write timeout, phy=%d reg=%d\n",
+ phy, reg);
+
+ return (0);
+}
+
+static void
+awg_update_link_locked(struct awg_softc *sc)
+{
+ struct mii_data *mii;
+ uint32_t val;
+
+ AWG_ASSERT_LOCKED(sc);
+
+ if ((if_getdrvflags(sc->ifp) & IFF_DRV_RUNNING) == 0)
+ return;
+ mii = device_get_softc(sc->miibus);
+
+ if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
+ (IFM_ACTIVE | IFM_AVALID)) {
+ switch (IFM_SUBTYPE(mii->mii_media_active)) {
+ case IFM_1000_T:
+ case IFM_1000_SX:
+ case IFM_100_TX:
+ case IFM_10_T:
+ sc->link = 1;
+ break;
+ default:
+ sc->link = 0;
+ break;
+ }
+ } else
+ sc->link = 0;
+
+ if (sc->link == 0)
+ return;
+
+ val = RD4(sc, EMAC_BASIC_CTL_0);
+ val &= ~(BASIC_CTL_SPEED | BASIC_CTL_DUPLEX);
+
+ if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T ||
+ IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_SX)
+ val |= BASIC_CTL_SPEED_1000 << BASIC_CTL_SPEED_SHIFT;
+ else if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX)
+ val |= BASIC_CTL_SPEED_100 << BASIC_CTL_SPEED_SHIFT;
+ else
+ val |= BASIC_CTL_SPEED_10 << BASIC_CTL_SPEED_SHIFT;
+
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0)
+ val |= BASIC_CTL_DUPLEX;
+
+ WR4(sc, EMAC_BASIC_CTL_0, val);
+
+ val = RD4(sc, EMAC_RX_CTL_0);
+ val &= ~RX_FLOW_CTL_EN;
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0)
+ val |= RX_FLOW_CTL_EN;
+ WR4(sc, EMAC_RX_CTL_0, val);
+
+ val = RD4(sc, EMAC_TX_FLOW_CTL);
+ val &= ~(PAUSE_TIME|TX_FLOW_CTL_EN);
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0)
+ val |= TX_FLOW_CTL_EN;
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0)
+ val |= awg_pause_time << PAUSE_TIME_SHIFT;
+ WR4(sc, EMAC_TX_FLOW_CTL, val);
+}
+
+static void
+awg_link_task(void *arg, int pending)
+{
+ struct awg_softc *sc;
+
+ sc = arg;
+
+ AWG_LOCK(sc);
+ awg_update_link_locked(sc);
+ AWG_UNLOCK(sc);
+}
+
+static void
+awg_miibus_statchg(device_t dev)
+{
+ struct awg_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ taskqueue_enqueue(taskqueue_swi, &sc->link_task);
+}
+
+static void
+awg_media_status(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct awg_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = device_get_softc(sc->miibus);
+
+ AWG_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ AWG_UNLOCK(sc);
+}
+
+static int
+awg_media_change(if_t ifp)
+{
+ struct awg_softc *sc;
+ struct mii_data *mii;
+ int error;
+
+ sc = if_getsoftc(ifp);
+ mii = device_get_softc(sc->miibus);
+
+ AWG_LOCK(sc);
+ error = mii_mediachg(mii);
+ AWG_UNLOCK(sc);
+
+ return (error);
+}
+
+static void
+awg_setup_txdesc(struct awg_softc *sc, int index, int flags, bus_addr_t paddr,
+ u_int len)
+{
+ uint32_t status, size;
+
+ if (paddr == 0 || len == 0) {
+ status = 0;
+ size = 0;
+ --sc->tx.queued;
+ } else {
+ status = TX_DESC_CTL;
+ size = flags | len;
+ if ((index & (awg_tx_interval - 1)) == 0)
+ size |= htole32(TX_INT_CTL);
+ ++sc->tx.queued;
+ }
+
+ sc->tx.desc_ring[index].addr = htole32((uint32_t)paddr);
+ sc->tx.desc_ring[index].size = htole32(size);
+ sc->tx.desc_ring[index].status = htole32(status);
+}
+
+static int
+awg_setup_txbuf(struct awg_softc *sc, int index, struct mbuf **mp)
+{
+ bus_dma_segment_t segs[TX_MAX_SEGS];
+ int error, nsegs, cur, i, flags;
+ u_int csum_flags;
+ struct mbuf *m;
+
+ m = *mp;
+ error = bus_dmamap_load_mbuf_sg(sc->tx.buf_tag,
+ sc->tx.buf_map[index].map, m, segs, &nsegs, BUS_DMA_NOWAIT);
+ if (error == EFBIG) {
+ m = m_collapse(m, M_NOWAIT, TX_MAX_SEGS);
+ if (m == NULL)
+ return (0);
+ *mp = m;
+ error = bus_dmamap_load_mbuf_sg(sc->tx.buf_tag,
+ sc->tx.buf_map[index].map, m, segs, &nsegs, BUS_DMA_NOWAIT);
+ }
+ if (error != 0)
+ return (0);
+
+ bus_dmamap_sync(sc->tx.buf_tag, sc->tx.buf_map[index].map,
+ BUS_DMASYNC_PREWRITE);
+
+ flags = TX_FIR_DESC;
+ if ((m->m_pkthdr.csum_flags & CSUM_IP) != 0) {
+ if ((m->m_pkthdr.csum_flags & (CSUM_TCP|CSUM_UDP)) != 0)
+ csum_flags = TX_CHECKSUM_CTL_FULL;
+ else
+ csum_flags = TX_CHECKSUM_CTL_IP;
+ flags |= (csum_flags << TX_CHECKSUM_CTL_SHIFT);
+ }
+
+ for (cur = index, i = 0; i < nsegs; i++) {
+ sc->tx.buf_map[cur].mbuf = (i == 0 ? m : NULL);
+ if (i == nsegs - 1)
+ flags |= TX_LAST_DESC;
+ awg_setup_txdesc(sc, cur, flags, segs[i].ds_addr,
+ segs[i].ds_len);
+ flags &= ~TX_FIR_DESC;
+ cur = TX_NEXT(cur);
+ }
+
+ return (nsegs);
+}
+
+static void
+awg_setup_rxdesc(struct awg_softc *sc, int index, bus_addr_t paddr)
+{
+ uint32_t status, size;
+
+ status = RX_DESC_CTL;
+ size = MCLBYTES - 1;
+
+ sc->rx.desc_ring[index].addr = htole32((uint32_t)paddr);
+ sc->rx.desc_ring[index].size = htole32(size);
+ sc->rx.desc_ring[index].next =
+ htole32(sc->rx.desc_ring_paddr + DESC_OFF(RX_NEXT(index)));
+ sc->rx.desc_ring[index].status = htole32(status);
+}
+
+static int
+awg_setup_rxbuf(struct awg_softc *sc, int index, struct mbuf *m)
+{
+ bus_dma_segment_t seg;
+ int error, nsegs;
+
+ m_adj(m, ETHER_ALIGN);
+
+ error = bus_dmamap_load_mbuf_sg(sc->rx.buf_tag,
+ sc->rx.buf_map[index].map, m, &seg, &nsegs, 0);
+ if (error != 0)
+ return (error);
+
+ bus_dmamap_sync(sc->rx.buf_tag, sc->rx.buf_map[index].map,
+ BUS_DMASYNC_PREREAD);
+
+ sc->rx.buf_map[index].mbuf = m;
+ awg_setup_rxdesc(sc, index, seg.ds_addr);
+
+ return (0);
+}
+
+static struct mbuf *
+awg_alloc_mbufcl(struct awg_softc *sc)
+{
+ struct mbuf *m;
+
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (m != NULL)
+ m->m_pkthdr.len = m->m_len = m->m_ext.ext_size;
+
+ return (m);
+}
+
+static void
+awg_start_locked(struct awg_softc *sc)
+{
+ struct mbuf *m;
+ uint32_t val;
+ if_t ifp;
+ int cnt, nsegs;
+
+ AWG_ASSERT_LOCKED(sc);
+
+ if (!sc->link)
+ return;
+
+ ifp = sc->ifp;
+
+ if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) !=
+ IFF_DRV_RUNNING)
+ return;
+
+ for (cnt = 0; ; cnt++) {
+ if (sc->tx.queued >= TX_DESC_COUNT - TX_MAX_SEGS) {
+ if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
+ break;
+ }
+
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ break;
+
+ nsegs = awg_setup_txbuf(sc, sc->tx.cur, &m);
+ if (nsegs == 0) {
+ if_sendq_prepend(ifp, m);
+ break;
+ }
+ if_bpfmtap(ifp, m);
+ sc->tx.cur = TX_SKIP(sc->tx.cur, nsegs);
+ }
+
+ if (cnt != 0) {
+ bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map,
+ BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
+
+ /* Start and run TX DMA */
+ val = RD4(sc, EMAC_TX_CTL_1);
+ WR4(sc, EMAC_TX_CTL_1, val | TX_DMA_START);
+ }
+}
+
+static void
+awg_start(if_t ifp)
+{
+ struct awg_softc *sc;
+
+ sc = if_getsoftc(ifp);
+
+ AWG_LOCK(sc);
+ awg_start_locked(sc);
+ AWG_UNLOCK(sc);
+}
+
+static void
+awg_tick(void *softc)
+{
+ struct awg_softc *sc;
+ struct mii_data *mii;
+ if_t ifp;
+ int link;
+
+ sc = softc;
+ ifp = sc->ifp;
+ mii = device_get_softc(sc->miibus);
+
+ AWG_ASSERT_LOCKED(sc);
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ return;
+
+ link = sc->link;
+ mii_tick(mii);
+ if (sc->link && !link)
+ awg_start_locked(sc);
+
+ callout_reset(&sc->stat_ch, hz, awg_tick, sc);
+}
+
+/* Bit Reversal - http://aggregate.org/MAGIC/#Bit%20Reversal */
+static uint32_t
+bitrev32(uint32_t x)
+{
+ x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
+ x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
+ x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
+ x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
+
+ return (x >> 16) | (x << 16);
+}
+
+static void
+awg_setup_rxfilter(struct awg_softc *sc)
+{
+ uint32_t val, crc, hashreg, hashbit, hash[2], machi, maclo;
+ int mc_count, mcnt, i;
+ uint8_t *eaddr, *mta;
+ if_t ifp;
+
+ AWG_ASSERT_LOCKED(sc);
+
+ ifp = sc->ifp;
+ val = 0;
+ hash[0] = hash[1] = 0;
+
+ mc_count = if_multiaddr_count(ifp, -1);
+
+ if (if_getflags(ifp) & IFF_PROMISC)
+ val |= DIS_ADDR_FILTER;
+ else if (if_getflags(ifp) & IFF_ALLMULTI) {
+ val |= RX_ALL_MULTICAST;
+ hash[0] = hash[1] = ~0;
+ } else if (mc_count > 0) {
+ val |= HASH_MULTICAST;
+
+ mta = malloc(sizeof(unsigned char) * ETHER_ADDR_LEN * mc_count,
+ M_DEVBUF, M_NOWAIT);
+ if (mta == NULL) {
+ if_printf(ifp,
+ "failed to allocate temporary multicast list\n");
+ return;
+ }
+
+ if_multiaddr_array(ifp, mta, &mcnt, mc_count);
+ for (i = 0; i < mcnt; i++) {
+ crc = ether_crc32_le(mta + (i * ETHER_ADDR_LEN),
+ ETHER_ADDR_LEN) & 0x7f;
+ crc = bitrev32(~crc) >> 26;
+ hashreg = (crc >> 5);
+ hashbit = (crc & 0x1f);
+ hash[hashreg] |= (1 << hashbit);
+ }
+
+ free(mta, M_DEVBUF);
+ }
+
+ /* Write our unicast address */
+ eaddr = IF_LLADDR(ifp);
+ machi = (eaddr[5] << 8) | eaddr[4];
+ maclo = (eaddr[3] << 24) | (eaddr[2] << 16) | (eaddr[1] << 8) |
+ (eaddr[0] << 0);
+ WR4(sc, EMAC_ADDR_HIGH(0), machi);
+ WR4(sc, EMAC_ADDR_LOW(0), maclo);
+
+ /* Multicast hash filters */
+ WR4(sc, EMAC_RX_HASH_0, hash[1]);
+ WR4(sc, EMAC_RX_HASH_1, hash[0]);
+
+ /* RX frame filter config */
+ WR4(sc, EMAC_RX_FRM_FLT, val);
+}
+
+static void
+awg_init_locked(struct awg_softc *sc)
+{
+ struct mii_data *mii;
+ uint32_t val;
+ if_t ifp;
+
+ mii = device_get_softc(sc->miibus);
+ ifp = sc->ifp;
+
+ AWG_ASSERT_LOCKED(sc);
+
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
+ return;
+
+ awg_setup_rxfilter(sc);
+
+ /* Configure DMA burst length and priorities */
+ val = awg_burst_len << BASIC_CTL_BURST_LEN_SHIFT;
+ if (awg_rx_tx_pri)
+ val |= BASIC_CTL_RX_TX_PRI;
+ WR4(sc, EMAC_BASIC_CTL_1, val);
+
+ /* Enable interrupts */
+ WR4(sc, EMAC_INT_EN, RX_INT_EN | TX_INT_EN | TX_BUF_UA_INT_EN);
+
+ /* Enable transmit DMA */
+ val = RD4(sc, EMAC_TX_CTL_1);
+ WR4(sc, EMAC_TX_CTL_1, val | TX_DMA_EN | TX_MD);
+
+ /* Enable receive DMA */
+ val = RD4(sc, EMAC_RX_CTL_1);
+ WR4(sc, EMAC_RX_CTL_1, val | RX_DMA_EN | RX_MD);
+
+ /* Enable transmitter */
+ val = RD4(sc, EMAC_TX_CTL_0);
+ WR4(sc, EMAC_TX_CTL_0, val | TX_EN);
+
+ /* Enable receiver */
+ val = RD4(sc, EMAC_RX_CTL_0);
+ WR4(sc, EMAC_RX_CTL_0, val | RX_EN | CHECK_CRC);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE);
+
+ mii_mediachg(mii);
+ callout_reset(&sc->stat_ch, hz, awg_tick, sc);
+}
+
+static void
+awg_init(void *softc)
+{
+ struct awg_softc *sc;
+
+ sc = softc;
+
+ AWG_LOCK(sc);
+ awg_init_locked(sc);
+ AWG_UNLOCK(sc);
+}
+
+static void
+awg_stop(struct awg_softc *sc)
+{
+ if_t ifp;
+ uint32_t val;
+
+ AWG_ASSERT_LOCKED(sc);
+
+ ifp = sc->ifp;
+
+ callout_stop(&sc->stat_ch);
+
+ /* Stop transmit DMA and flush data in the TX FIFO */
+ val = RD4(sc, EMAC_TX_CTL_1);
+ val &= ~TX_DMA_EN;
+ val |= FLUSH_TX_FIFO;
+ WR4(sc, EMAC_TX_CTL_1, val);
+
+ /* Disable transmitter */
+ val = RD4(sc, EMAC_TX_CTL_0);
+ WR4(sc, EMAC_TX_CTL_0, val & ~TX_EN);
+
+ /* Disable receiver */
+ val = RD4(sc, EMAC_RX_CTL_0);
+ WR4(sc, EMAC_RX_CTL_0, val & ~RX_EN);
+
+ /* Disable interrupts */
+ WR4(sc, EMAC_INT_EN, 0);
+
+ /* Disable transmit DMA */
+ val = RD4(sc, EMAC_TX_CTL_1);
+ WR4(sc, EMAC_TX_CTL_1, val & ~TX_DMA_EN);
+
+ /* Disable receive DMA */
+ val = RD4(sc, EMAC_RX_CTL_1);
+ WR4(sc, EMAC_RX_CTL_1, val & ~RX_DMA_EN);
+
+ sc->link = 0;
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
+}
+
+static void
+awg_rxintr(struct awg_softc *sc)
+{
+ if_t ifp;
+ struct mbuf *m, *m0;
+ int error, index, len;
+ uint32_t status;
+
+ ifp = sc->ifp;
+
+ bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map,
+ BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
+
+ for (index = sc->rx.cur; ; index = RX_NEXT(index)) {
+ status = le32toh(sc->rx.desc_ring[index].status);
+ if ((status & RX_DESC_CTL) != 0)
+ break;
+
+ bus_dmamap_sync(sc->rx.buf_tag, sc->rx.buf_map[index].map,
+ BUS_DMASYNC_POSTREAD);
+ bus_dmamap_unload(sc->rx.buf_tag, sc->rx.buf_map[index].map);
+
+ len = (status & RX_FRM_LEN) >> RX_FRM_LEN_SHIFT;
+ if (len != 0) {
+ m = sc->rx.buf_map[index].mbuf;
+ m->m_pkthdr.rcvif = ifp;
+ m->m_pkthdr.len = len;
+ m->m_len = len;
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+
+ if ((if_getcapenable(ifp) & IFCAP_RXCSUM) != 0 &&
+ (status & RX_FRM_TYPE) != 0) {
+ m->m_pkthdr.csum_flags = CSUM_IP_CHECKED;
+ if ((status & RX_HEADER_ERR) == 0)
+ m->m_pkthdr.csum_flags |= CSUM_IP_VALID;
+ if ((status & RX_PAYLOAD_ERR) == 0) {
+ m->m_pkthdr.csum_flags |=
+ CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
+ m->m_pkthdr.csum_data = 0xffff;
+ }
+ }
+
+ AWG_UNLOCK(sc);
+ if_input(ifp, m);
+ AWG_LOCK(sc);
+ }
+
+ if ((m0 = awg_alloc_mbufcl(sc)) != NULL) {
+ error = awg_setup_rxbuf(sc, index, m0);
+ if (error != 0) {
+ /* XXX hole in RX ring */
+ }
+ } else
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ }
+
+ if (index != sc->rx.cur) {
+ bus_dmamap_sync(sc->rx.desc_tag, sc->rx.desc_map,
+ BUS_DMASYNC_PREWRITE);
+ }
+
+ sc->rx.cur = index;
+}
+
+static void
+awg_txintr(struct awg_softc *sc)
+{
+ struct awg_bufmap *bmap;
+ struct emac_desc *desc;
+ uint32_t status;
+ if_t ifp;
+ int i;
+
+ AWG_ASSERT_LOCKED(sc);
+
+ bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map,
+ BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
+
+ ifp = sc->ifp;
+ for (i = sc->tx.next; sc->tx.queued > 0; i = TX_NEXT(i)) {
+ desc = &sc->tx.desc_ring[i];
+ status = le32toh(desc->status);
+ if ((status & TX_DESC_CTL) != 0)
+ break;
+ bmap = &sc->tx.buf_map[i];
+ if (bmap->mbuf != NULL) {
+ bus_dmamap_sync(sc->tx.buf_tag, bmap->map,
+ BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(sc->tx.buf_tag, bmap->map);
+ m_freem(bmap->mbuf);
+ bmap->mbuf = NULL;
+ }
+ awg_setup_txdesc(sc, i, 0, 0, 0);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+ }
+
+ sc->tx.next = i;
+
+ bus_dmamap_sync(sc->tx.desc_tag, sc->tx.desc_map,
+ BUS_DMASYNC_PREWRITE);
+}
+
+static void
+awg_intr(void *arg)
+{
+ struct awg_softc *sc;
+ uint32_t val;
+
+ sc = arg;
+
+ AWG_LOCK(sc);
+ val = RD4(sc, EMAC_INT_STA);
+ WR4(sc, EMAC_INT_STA, val);
+
+ if (val & RX_INT)
+ awg_rxintr(sc);
+
+ if (val & (TX_INT|TX_BUF_UA_INT)) {
+ awg_txintr(sc);
+ if (!if_sendq_empty(sc->ifp))
+ awg_start_locked(sc);
+ }
+
+ AWG_UNLOCK(sc);
+}
+
+static int
+awg_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct awg_softc *sc;
+ struct mii_data *mii;
+ struct ifreq *ifr;
+ int flags, mask, error;
+
+ sc = if_getsoftc(ifp);
+ mii = device_get_softc(sc->miibus);
+ ifr = (struct ifreq *)data;
+ error = 0;
+
+ switch (cmd) {
+ case SIOCSIFFLAGS:
+ AWG_LOCK(sc);
+ if (if_getflags(ifp) & IFF_UP) {
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
+ flags = if_getflags(ifp) ^ sc->if_flags;
+ if ((flags & (IFF_PROMISC|IFF_ALLMULTI)) != 0)
+ awg_setup_rxfilter(sc);
+ } else
+ awg_init_locked(sc);
+ } else {
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
+ awg_stop(sc);
+ }
+ sc->if_flags = if_getflags(ifp);
+ AWG_UNLOCK(sc);
+ break;
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
+ AWG_LOCK(sc);
+ awg_setup_rxfilter(sc);
+ AWG_UNLOCK(sc);
+ }
+ break;
+ case SIOCSIFMEDIA:
+ case SIOCGIFMEDIA:
+ error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd);
+ break;
+ case SIOCSIFCAP:
+ mask = ifr->ifr_reqcap ^ if_getcapenable(ifp);
+ if (mask & IFCAP_VLAN_MTU)
+ if_togglecapenable(ifp, IFCAP_VLAN_MTU);
+ if (mask & IFCAP_RXCSUM)
+ if_togglecapenable(ifp, IFCAP_RXCSUM);
+ if (mask & IFCAP_TXCSUM)
+ if_togglecapenable(ifp, IFCAP_TXCSUM);
+ if ((if_getcapenable(ifp) & (IFCAP_RXCSUM|IFCAP_TXCSUM)) != 0)
+ if_sethwassistbits(ifp, CSUM_IP, 0);
+ else
+ if_sethwassistbits(ifp, 0, CSUM_IP);
+ break;
+ default:
+ error = ether_ioctl(ifp, cmd, data);
+ break;
+ }
+
+ return (error);
+}
+
+static int
+awg_setup_extres(device_t dev)
+{
+ struct awg_softc *sc;
+ hwreset_t rst_ahb;
+ clk_t clk_ahb, clk_tx, clk_tx_parent;
+ regulator_t reg;
+ const char *tx_parent_name;
+ char *phy_type;
+ phandle_t node;
+ uint64_t freq;
+ int error, div;
+
+ sc = device_get_softc(dev);
+ node = ofw_bus_get_node(dev);
+ rst_ahb = NULL;
+ clk_ahb = NULL;
+ clk_tx = NULL;
+ clk_tx_parent = NULL;
+ reg = NULL;
+ phy_type = NULL;
+
+ /* Get AHB clock and reset resources */
+ error = hwreset_get_by_ofw_name(dev, "ahb", &rst_ahb);
+ if (error != 0) {
+ device_printf(dev, "cannot get ahb reset\n");
+ goto fail;
+ }
+ error = clk_get_by_ofw_name(dev, "ahb", &clk_ahb);
+ if (error != 0) {
+ device_printf(dev, "cannot get ahb clock\n");
+ goto fail;
+ }
+
+ /* Configure PHY for MII or RGMII mode */
+ if (OF_getprop_alloc(node, "phy-mode", 1, (void **)&phy_type)) {
+ if (bootverbose)
+ device_printf(dev, "PHY type: %s\n", phy_type);
+
+ if (strcmp(phy_type, "rgmii") == 0)
+ tx_parent_name = "emac_int_tx";
+ else
+ tx_parent_name = "mii_phy_tx";
+ free(phy_type, M_OFWPROP);
+
+ /* Get the TX clock */
+ error = clk_get_by_ofw_name(dev, "tx", &clk_tx);
+ if (error != 0) {
+ device_printf(dev, "cannot get tx clock\n");
+ goto fail;
+ }
+
+ /* Find the desired parent clock based on phy-mode property */
+ error = clk_get_by_name(dev, tx_parent_name, &clk_tx_parent);
+ if (error != 0) {
+ device_printf(dev, "cannot get clock '%s'\n",
+ tx_parent_name);
+ goto fail;
+ }
+
+ /* Set TX clock parent */
+ error = clk_set_parent_by_clk(clk_tx, clk_tx_parent);
+ if (error != 0) {
+ device_printf(dev, "cannot set tx clock parent\n");
+ goto fail;
+ }
+
+ /* Enable TX clock */
+ error = clk_enable(clk_tx);
+ if (error != 0) {
+ device_printf(dev, "cannot enable tx clock\n");
+ goto fail;
+ }
+ }
+
+ /* Enable AHB clock */
+ error = clk_enable(clk_ahb);
*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
More information about the svn-src-all
mailing list