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 <jmcne...@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 ***
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to