/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2019 Ruslan Bukin <br@bsdpad.com>
 *
 * This software was developed by SRI International and the University of
 * Cambridge Computer Laboratory (Department of Computer Science and
 * Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
 * DARPA SSITH research programme.
 *
 * 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 AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/rman.h>
#include <sys/socket.h>
#include <sys/sockio.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/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/mii/tiphy.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/xilinx/if_xaereg.h>
#include <dev/xilinx/if_xaevar.h>

#include <dev/xilinx/axidma.h>

#include "miibus_if.h"

#define	READ4(_sc, _reg) \
	bus_read_4((_sc)->res[0], _reg)
#define	WRITE4(_sc, _reg, _val) \
	bus_write_4((_sc)->res[0], _reg, _val)

#define	READ8(_sc, _reg) \
	bus_read_8((_sc)->res[0], _reg)
#define	WRITE8(_sc, _reg, _val) \
	bus_write_8((_sc)->res[0], _reg, _val)

#define	XAE_LOCK(sc)			mtx_lock(&(sc)->mtx)
#define	XAE_UNLOCK(sc)			mtx_unlock(&(sc)->mtx)
#define	XAE_ASSERT_LOCKED(sc)		mtx_assert(&(sc)->mtx, MA_OWNED)
#define	XAE_ASSERT_UNLOCKED(sc)		mtx_assert(&(sc)->mtx, MA_NOTOWNED)

#define XAE_DEBUG
#undef XAE_DEBUG

#ifdef XAE_DEBUG
#define dprintf(fmt, ...)  printf(fmt, ##__VA_ARGS__)
#else
#define dprintf(fmt, ...)
#endif

#define	RX_QUEUE_SIZE		64
#define	TX_QUEUE_SIZE		64
#define	NUM_RX_MBUF		16
#define	BUFRING_SIZE		8192
#define	MDIO_CLK_DIV_DEFAULT	29

#define	PHY1_RD(sc, _r)		\
	xae_miibus_read_reg(sc->dev, 1, _r)
#define	PHY1_WR(sc, _r, _v)	\
	xae_miibus_write_reg(sc->dev, 1, _r, _v)

#define	PHY_RD(sc, _r)		\
	xae_miibus_read_reg(sc->dev, sc->phy_addr, _r)
#define	PHY_WR(sc, _r, _v)	\
	xae_miibus_write_reg(sc->dev, sc->phy_addr, _r, _v)

/* Use this macro to access regs > 0x1f */
#define WRITE_TI_EREG(sc, reg, data) {					\
	PHY_WR(sc, MII_MMDACR, MMDACR_DADDRMASK);			\
	PHY_WR(sc, MII_MMDAADR, reg);					\
	PHY_WR(sc, MII_MMDACR, MMDACR_DADDRMASK | MMDACR_FN_DATANPI);	\
	PHY_WR(sc, MII_MMDAADR, data);					\
}

/* Not documented, Xilinx VCU118 workaround */
#define	 CFG4_SGMII_TMR			0x160 /* bits 8:7 MUST be '10' */
#define	DP83867_SGMIICTL1		0xD3 /* not documented register */
#define	 SGMIICTL1_SGMII_6W		(1 << 14) /* no idea what it is */

static struct resource_spec xae_spec[] = {
	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
	{ -1, 0 }
};

static void xae_stop_locked(struct xae_softc *sc);
static void xae_setup_rxfilter(struct xae_softc *sc);

static int
xae_rx_enqueue(struct xae_softc *sc, uint32_t n)
{
	struct mbuf *m;
	int i;

	for (i = 0; i < n; i++) {
		m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
		if (m == NULL) {
			device_printf(sc->dev,
			    "%s: Can't alloc rx mbuf\n", __func__);
			return (-1);
		}

		m->m_pkthdr.len = m->m_len = m->m_ext.ext_size;
		xdma_enqueue_mbuf(sc->xchan_rx, &m, 0, 4, 4, XDMA_DEV_TO_MEM);
	}

	return (0);
}

static int
xae_get_phyaddr(phandle_t node, int *phy_addr)
{
	phandle_t phy_node;
	pcell_t phy_handle, phy_reg;

	if (OF_getencprop(node, "phy-handle", (void *)&phy_handle,
	    sizeof(phy_handle)) <= 0)
		return (ENXIO);

	phy_node = OF_node_from_xref(phy_handle);

	if (OF_getencprop(phy_node, "reg", (void *)&phy_reg,
	    sizeof(phy_reg)) <= 0)
		return (ENXIO);

	*phy_addr = phy_reg;

	return (0);
}

static int
xae_xdma_tx_intr(void *arg, xdma_transfer_status_t *status)
{
	xdma_transfer_status_t st;
	struct xae_softc *sc;
	if_t ifp;
	struct mbuf *m;
	int err;

	sc = arg;

	XAE_LOCK(sc);

	ifp = sc->ifp;

	for (;;) {
		err = xdma_dequeue_mbuf(sc->xchan_tx, &m, &st);
		if (err != 0) {
			break;
		}

		if (st.error != 0) {
			if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
		}

		m_freem(m);
	}

	if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);

	XAE_UNLOCK(sc);

	return (0);
}

static int
xae_xdma_rx_intr(void *arg, xdma_transfer_status_t *status)
{
	xdma_transfer_status_t st;
	struct xae_softc *sc;
	if_t ifp;
	struct mbuf *m;
	int err;
	uint32_t cnt_processed;

	sc = arg;

	dprintf("%s\n", __func__);

	XAE_LOCK(sc);

	ifp = sc->ifp;

	cnt_processed = 0;
	for (;;) {
		err = xdma_dequeue_mbuf(sc->xchan_rx, &m, &st);
		if (err != 0) {
			break;
		}
		cnt_processed++;

		if (st.error != 0) {
			if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
			m_freem(m);
			continue;
		}

		m->m_pkthdr.len = m->m_len = st.transferred;
		m->m_pkthdr.rcvif = ifp;
		XAE_UNLOCK(sc);
		if_input(ifp, m);
		XAE_LOCK(sc);
	}

	xae_rx_enqueue(sc, cnt_processed);

	XAE_UNLOCK(sc);

	return (0);
}

static void
xae_qflush(if_t ifp)
{
}

static int
xae_transmit_locked(if_t ifp)
{
	struct xae_softc *sc;
	struct mbuf *m;
	struct buf_ring *br;
	int error;
	int enq;

	dprintf("%s\n", __func__);

	sc = if_getsoftc(ifp);
	br = sc->br;

	enq = 0;

	while ((m = drbr_peek(ifp, br)) != NULL) {
		error = xdma_enqueue_mbuf(sc->xchan_tx,
		    &m, 0, 4, 4, XDMA_MEM_TO_DEV);
		if (error != 0) {
			/* No space in request queue available yet. */
			drbr_putback(ifp, br, m);
			break;
		}

		drbr_advance(ifp, br);

		enq++;

		/* If anyone is interested give them a copy. */
		ETHER_BPF_MTAP(ifp, m);
        }

	if (enq > 0)
		xdma_queue_submit(sc->xchan_tx);

	return (0);
}

static int
xae_transmit(if_t ifp, struct mbuf *m)
{
	struct xae_softc *sc;
	int error;

	dprintf("%s\n", __func__);

	sc = if_getsoftc(ifp);

	XAE_LOCK(sc);

	error = drbr_enqueue(ifp, sc->br, m);
	if (error) {
		XAE_UNLOCK(sc);
		return (error);
	}

	if ((if_getdrvflags(ifp) & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) !=
	    IFF_DRV_RUNNING) {
		XAE_UNLOCK(sc);
		return (0);
	}

	if (!sc->link_is_up) {
		XAE_UNLOCK(sc);
		return (0);
	}

	error = xae_transmit_locked(ifp);

	XAE_UNLOCK(sc);

	return (error);
}

static void
xae_stop_locked(struct xae_softc *sc)
{
	if_t ifp;
	uint32_t reg;

	XAE_ASSERT_LOCKED(sc);

	ifp = sc->ifp;
	if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE));

	callout_stop(&sc->xae_callout);

	/* Stop the transmitter */
	reg = READ4(sc, XAE_TC);
	reg &= ~TC_TX;
	WRITE4(sc, XAE_TC, reg);

	/* Stop the receiver. */
	reg = READ4(sc, XAE_RCW1);
	reg &= ~RCW1_RX;
	WRITE4(sc, XAE_RCW1, reg);
}

static uint64_t
xae_stat(struct xae_softc *sc, int counter_id)
{
	uint64_t new, old;
	uint64_t delta;

	KASSERT(counter_id < XAE_MAX_COUNTERS,
		("counter %d is out of range", counter_id));

	new = READ8(sc, XAE_STATCNT(counter_id));
	old = sc->counters[counter_id];

	if (new >= old)
		delta = new - old;
	else
		delta = UINT64_MAX - old + new;
	sc->counters[counter_id] = new;

	return (delta);
}

static void
xae_harvest_stats(struct xae_softc *sc)
{
	if_t ifp;

	ifp = sc->ifp;

	if_inc_counter(ifp, IFCOUNTER_IPACKETS, xae_stat(sc, RX_GOOD_FRAMES));
	if_inc_counter(ifp, IFCOUNTER_IMCASTS, xae_stat(sc, RX_GOOD_MCASTS));
	if_inc_counter(ifp, IFCOUNTER_IERRORS,
	    xae_stat(sc, RX_FRAME_CHECK_SEQ_ERROR) +
	    xae_stat(sc, RX_LEN_OUT_OF_RANGE) +
	    xae_stat(sc, RX_ALIGNMENT_ERRORS));

	if_inc_counter(ifp, IFCOUNTER_OBYTES, xae_stat(sc, TX_BYTES));
	if_inc_counter(ifp, IFCOUNTER_OPACKETS, xae_stat(sc, TX_GOOD_FRAMES));
	if_inc_counter(ifp, IFCOUNTER_OMCASTS, xae_stat(sc, TX_GOOD_MCASTS));
	if_inc_counter(ifp, IFCOUNTER_OERRORS,
	    xae_stat(sc, TX_GOOD_UNDERRUN_ERRORS));

	if_inc_counter(ifp, IFCOUNTER_COLLISIONS,
	    xae_stat(sc, TX_SINGLE_COLLISION_FRAMES) +
	    xae_stat(sc, TX_MULTI_COLLISION_FRAMES) +
	    xae_stat(sc, TX_LATE_COLLISIONS) +
	    xae_stat(sc, TX_EXCESS_COLLISIONS));
}

static void
xae_tick(void *arg)
{
	struct xae_softc *sc;
	if_t ifp;
	int link_was_up;

	sc = arg;

	XAE_ASSERT_LOCKED(sc);

	ifp = sc->ifp;

	if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING))
		return;

	/* Gather stats from hardware counters. */
	xae_harvest_stats(sc);

	/* Check the media status. */
	link_was_up = sc->link_is_up;
	mii_tick(sc->mii_softc);
	if (sc->link_is_up && !link_was_up)
		xae_transmit_locked(sc->ifp);

	/* Schedule another check one second from now. */
	callout_reset(&sc->xae_callout, hz, xae_tick, sc);
}

static void
xae_init_locked(struct xae_softc *sc)
{
	if_t ifp;

	XAE_ASSERT_LOCKED(sc);

	ifp = sc->ifp;
	if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
		return;

	if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);

	xae_setup_rxfilter(sc);

	/* Enable the transmitter */
	WRITE4(sc, XAE_TC, TC_TX);

	/* Enable the receiver. */
	WRITE4(sc, XAE_RCW1, RCW1_RX);

	/*
	 * Call mii_mediachg() which will call back into xae_miibus_statchg()
	 * to set up the remaining config registers based on current media.
	 */
	mii_mediachg(sc->mii_softc);
	callout_reset(&sc->xae_callout, hz, xae_tick, sc);
}

static void
xae_init(void *arg)
{
	struct xae_softc *sc;

	sc = arg;

	XAE_LOCK(sc);
	xae_init_locked(sc);
	XAE_UNLOCK(sc);
}

static void
xae_media_status(if_t  ifp, struct ifmediareq *ifmr)
{
	struct xae_softc *sc;
	struct mii_data *mii;

	sc = if_getsoftc(ifp);
	mii = sc->mii_softc;

	XAE_LOCK(sc);
	mii_pollstat(mii);
	ifmr->ifm_active = mii->mii_media_active;
	ifmr->ifm_status = mii->mii_media_status;
	XAE_UNLOCK(sc);
}

static int
xae_media_change_locked(struct xae_softc *sc)
{

	return (mii_mediachg(sc->mii_softc));
}

static int
xae_media_change(if_t  ifp)
{
	struct xae_softc *sc;
	int error;

	sc = if_getsoftc(ifp);

	XAE_LOCK(sc);
	error = xae_media_change_locked(sc);
	XAE_UNLOCK(sc);

	return (error);
}

static u_int
xae_write_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
{
	struct xae_softc *sc = arg;
	uint32_t reg;
	uint8_t *ma;

	if (cnt >= XAE_MULTICAST_TABLE_SIZE)
		return (1);

	ma = LLADDR(sdl);

	reg = READ4(sc, XAE_FFC) & 0xffffff00;
	reg |= cnt;
	WRITE4(sc, XAE_FFC, reg);

	reg = (ma[0]);
	reg |= (ma[1] << 8);
	reg |= (ma[2] << 16);
	reg |= (ma[3] << 24);
	WRITE4(sc, XAE_FFV(0), reg);

	reg = ma[4];
	reg |= ma[5] << 8;
	WRITE4(sc, XAE_FFV(1), reg);

	return (1);
}

static void
xae_setup_rxfilter(struct xae_softc *sc)
{
	if_t ifp;
	uint32_t reg;

	XAE_ASSERT_LOCKED(sc);

	ifp = sc->ifp;

	/*
	 * Set the multicast (group) filter hash.
	 */
	if ((if_getflags(ifp) & (IFF_ALLMULTI | IFF_PROMISC)) != 0) {
		reg = READ4(sc, XAE_FFC);
		reg |= FFC_PM;
		WRITE4(sc, XAE_FFC, reg);
	} else {
		reg = READ4(sc, XAE_FFC);
		reg &= ~FFC_PM;
		WRITE4(sc, XAE_FFC, reg);

		if_foreach_llmaddr(ifp, xae_write_maddr, sc);
	}

	/*
	 * Set the primary address.
	 */
	reg = sc->macaddr[0];
	reg |= (sc->macaddr[1] << 8);
	reg |= (sc->macaddr[2] << 16);
	reg |= (sc->macaddr[3] << 24);
	WRITE4(sc, XAE_UAW0, reg);

	reg = sc->macaddr[4];
	reg |= (sc->macaddr[5] << 8);
	WRITE4(sc, XAE_UAW1, reg);
}

static int
xae_ioctl(if_t ifp, u_long cmd, caddr_t data)
{
	struct xae_softc *sc;
	struct mii_data *mii;
	struct ifreq *ifr;
	int mask, error;

	sc = if_getsoftc(ifp);
	ifr = (struct ifreq *)data;

	error = 0;
	switch (cmd) {
	case SIOCSIFFLAGS:
		XAE_LOCK(sc);
		if (if_getflags(ifp) & IFF_UP) {
			if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
				if ((if_getflags(ifp) ^ sc->if_flags) &
				    (IFF_PROMISC | IFF_ALLMULTI))
					xae_setup_rxfilter(sc);
			} else {
				if (!sc->is_detaching)
					xae_init_locked(sc);
			}
		} else {
			if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
				xae_stop_locked(sc);
		}
		sc->if_flags = if_getflags(ifp);
		XAE_UNLOCK(sc);
		break;
	case SIOCADDMULTI:
	case SIOCDELMULTI:
		if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
			XAE_LOCK(sc);
			xae_setup_rxfilter(sc);
			XAE_UNLOCK(sc);
		}
		break;
	case SIOCSIFMEDIA:
	case SIOCGIFMEDIA:
		mii = sc->mii_softc;
		error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd);
		break;
	case SIOCSIFCAP:
		mask = if_getcapenable(ifp) ^ ifr->ifr_reqcap;
		if (mask & IFCAP_VLAN_MTU) {
			/* No work to do except acknowledge the change took */
			if_togglecapenable(ifp, IFCAP_VLAN_MTU);
		}
		break;

	default:
		error = ether_ioctl(ifp, cmd, data);
		break;
	}

	return (error);
}

static void
xae_intr(void *arg)
{

}

static int
xae_get_hwaddr(struct xae_softc *sc, uint8_t *hwaddr)
{
	phandle_t node;
	int len;

	node = ofw_bus_get_node(sc->dev);

	/* Check if there is property */
	if ((len = OF_getproplen(node, "local-mac-address")) <= 0)
		return (EINVAL);

	if (len != ETHER_ADDR_LEN)
		return (EINVAL);

	OF_getprop(node, "local-mac-address", hwaddr,
	    ETHER_ADDR_LEN);

	return (0);
}

static int
mdio_wait(struct xae_softc *sc)
{
	uint32_t reg;
	int timeout;

	timeout = 200;

	do {
		reg = READ4(sc, XAE_MDIO_CTRL);
		if (reg & MDIO_CTRL_READY)
			break;
		DELAY(1);
	} while (timeout--);

	if (timeout <= 0) {
		printf("Failed to get MDIO ready\n");
		return (1);
	}

	return (0);
}

static int
xae_miibus_read_reg(device_t dev, int phy, int reg)
{
	struct xae_softc *sc;
	uint32_t mii;
	int rv;

	sc = device_get_softc(dev);

	if (mdio_wait(sc))
		return (0);

	mii = MDIO_CTRL_TX_OP_READ | MDIO_CTRL_INITIATE;
	mii |= (reg << MDIO_TX_REGAD_S);
	mii |= (phy << MDIO_TX_PHYAD_S);

	WRITE4(sc, XAE_MDIO_CTRL, mii);

	if (mdio_wait(sc))
		return (0);

	rv = READ4(sc, XAE_MDIO_READ);

	return (rv);
}

static int
xae_miibus_write_reg(device_t dev, int phy, int reg, int val)
{
	struct xae_softc *sc;
	uint32_t mii;

	sc = device_get_softc(dev);

	if (mdio_wait(sc))
		return (1);

	mii = MDIO_CTRL_TX_OP_WRITE | MDIO_CTRL_INITIATE;
	mii |= (reg << MDIO_TX_REGAD_S);
	mii |= (phy << MDIO_TX_PHYAD_S);

	WRITE4(sc, XAE_MDIO_WRITE, val);
	WRITE4(sc, XAE_MDIO_CTRL, mii);

	if (mdio_wait(sc))
		return (1);

	return (0);
}

static void
xae_phy_fixup(struct xae_softc *sc)
{
	uint32_t reg;

	do {
		WRITE_TI_EREG(sc, DP83867_SGMIICTL1, SGMIICTL1_SGMII_6W);
		PHY_WR(sc, DP83867_PHYCR, PHYCR_SGMII_EN);

		reg = PHY_RD(sc, DP83867_CFG2);
		reg &= ~CFG2_SPEED_OPT_ATTEMPT_CNT_M;
		reg |= (CFG2_SPEED_OPT_ATTEMPT_CNT_4);
		reg |= CFG2_INTERRUPT_POLARITY;
		reg |= CFG2_SPEED_OPT_ENHANCED_EN;
		reg |= CFG2_SPEED_OPT_10M_EN;
		PHY_WR(sc, DP83867_CFG2, reg);

		WRITE_TI_EREG(sc, DP83867_CFG4, CFG4_SGMII_TMR);
		PHY_WR(sc, MII_BMCR,
		    BMCR_AUTOEN | BMCR_FDX | BMCR_SPEED1 | BMCR_RESET);
	} while (PHY1_RD(sc, MII_BMCR) == 0x0ffff);

	do {
		PHY1_WR(sc, MII_BMCR,
		    BMCR_AUTOEN | BMCR_FDX | BMCR_SPEED1 | BMCR_STARTNEG);
		DELAY(40000);
	} while ((PHY1_RD(sc, MII_BMSR) & BMSR_ACOMP) == 0);
}

static int
get_xdma_std(struct xae_softc *sc)
{

	sc->xdma_tx = xdma_ofw_get(sc->dev, "tx");
	if (sc->xdma_tx == NULL)
		return (ENXIO);

	sc->xdma_rx = xdma_ofw_get(sc->dev, "rx");
	if (sc->xdma_rx == NULL) {
		xdma_put(sc->xdma_tx);
		return (ENXIO);
	}

	return (0);
}

static int
get_xdma_axistream(struct xae_softc *sc)
{
	struct axidma_fdt_data *data;
	device_t dma_dev;
	phandle_t node;
	pcell_t prop;
	size_t len;

	node = ofw_bus_get_node(sc->dev);
	len = OF_getencprop(node, "axistream-connected", &prop, sizeof(prop));
	if (len != sizeof(prop)) {
		device_printf(sc->dev,
		    "%s: Couldn't get axistream-connected prop.\n", __func__);
		return (ENXIO);
	}
	dma_dev = OF_device_from_xref(prop);
	if (dma_dev == NULL) {
		device_printf(sc->dev, "Could not get DMA device by xref.\n");
		return (ENXIO);
	}

	sc->xdma_tx = xdma_get(sc->dev, dma_dev);
	if (sc->xdma_tx == NULL) {
		device_printf(sc->dev, "Could not find DMA controller.\n");
		return (ENXIO);
	}
	data = malloc(sizeof(struct axidma_fdt_data),
	    M_DEVBUF, (M_WAITOK | M_ZERO));
	data->id = AXIDMA_TX_CHAN;
	sc->xdma_tx->data = data;

	sc->xdma_rx = xdma_get(sc->dev, dma_dev);
	if (sc->xdma_rx == NULL) {
		device_printf(sc->dev, "Could not find DMA controller.\n");
		return (ENXIO);
	}
	data = malloc(sizeof(struct axidma_fdt_data),
	    M_DEVBUF, (M_WAITOK | M_ZERO));
	data->id = AXIDMA_RX_CHAN;
	sc->xdma_rx->data = data;

	return (0);
}

static int
setup_xdma(struct xae_softc *sc)
{
	device_t dev;
	vmem_t *vmem;
	int error;

	dev = sc->dev;

	/* Get xDMA controller */   
	error = get_xdma_std(sc);

	if (error) {
		device_printf(sc->dev,
		    "Fallback to axistream-connected property\n");
		error = get_xdma_axistream(sc);
	}

	if (error) {
		device_printf(dev, "Could not find xDMA controllers.\n");
		return (ENXIO);
	}

	/* Alloc xDMA TX virtual channel. */
	sc->xchan_tx = xdma_channel_alloc(sc->xdma_tx, 0);
	if (sc->xchan_tx == NULL) {
		device_printf(dev, "Can't alloc virtual DMA TX channel.\n");
		return (ENXIO);
	}

	/* Setup interrupt handler. */
	error = xdma_setup_intr(sc->xchan_tx, 0,
	    xae_xdma_tx_intr, sc, &sc->ih_tx);
	if (error) {
		device_printf(sc->dev,
		    "Can't setup xDMA TX interrupt handler.\n");
		return (ENXIO);
	}

	/* Alloc xDMA RX virtual channel. */
	sc->xchan_rx = xdma_channel_alloc(sc->xdma_rx, 0);
	if (sc->xchan_rx == NULL) {
		device_printf(dev, "Can't alloc virtual DMA RX channel.\n");
		return (ENXIO);
	}

	/* Setup interrupt handler. */
	error = xdma_setup_intr(sc->xchan_rx, XDMA_INTR_NET,
	    xae_xdma_rx_intr, sc, &sc->ih_rx);
	if (error) {
		device_printf(sc->dev,
		    "Can't setup xDMA RX interrupt handler.\n");
		return (ENXIO);
	}

	/* Setup bounce buffer */
	vmem = xdma_get_memory(dev);
	if (vmem) {
		xchan_set_memory(sc->xchan_tx, vmem);
		xchan_set_memory(sc->xchan_rx, vmem);
	}

	xdma_prep_sg(sc->xchan_tx,
	    TX_QUEUE_SIZE,	/* xchan requests queue size */
	    MCLBYTES,	/* maxsegsize */
	    8,		/* maxnsegs */
	    16,		/* alignment */
	    0,		/* boundary */
	    BUS_SPACE_MAXADDR_32BIT,
	    BUS_SPACE_MAXADDR);

	xdma_prep_sg(sc->xchan_rx,
	    RX_QUEUE_SIZE,	/* xchan requests queue size */
	    MCLBYTES,	/* maxsegsize */
	    1,		/* maxnsegs */
	    16,		/* alignment */
	    0,		/* boundary */
	    BUS_SPACE_MAXADDR_32BIT,
	    BUS_SPACE_MAXADDR);

	return (0);
}

static int
xae_probe(device_t dev)
{

	if (!ofw_bus_status_okay(dev))
		return (ENXIO);

	if (!ofw_bus_is_compatible(dev, "xlnx,axi-ethernet-1.00.a"))
		return (ENXIO);

	device_set_desc(dev, "Xilinx AXI Ethernet");

	return (BUS_PROBE_DEFAULT);
}

static int
xae_attach(device_t dev)
{
	struct xae_softc *sc;
	if_t ifp;
	phandle_t node;
	uint32_t reg;
	int error;

	sc = device_get_softc(dev);
	sc->dev = dev;
	node = ofw_bus_get_node(dev);

	if (setup_xdma(sc) != 0) {
		device_printf(dev, "Could not setup xDMA.\n");
		return (ENXIO);
	}

	mtx_init(&sc->mtx, device_get_nameunit(sc->dev),
	    MTX_NETWORK_LOCK, MTX_DEF);

	sc->br = buf_ring_alloc(BUFRING_SIZE, M_DEVBUF,
	    M_NOWAIT, &sc->mtx);
	if (sc->br == NULL)
		return (ENOMEM);

	if (bus_alloc_resources(dev, xae_spec, sc->res)) {
		device_printf(dev, "could not allocate resources\n");
		return (ENXIO);
	}

	/* Memory interface */
	sc->bst = rman_get_bustag(sc->res[0]);
	sc->bsh = rman_get_bushandle(sc->res[0]);

	device_printf(sc->dev, "Identification: %x\n",
	    READ4(sc, XAE_IDENT));

	/* Get MAC addr */
	if (xae_get_hwaddr(sc, sc->macaddr)) {
		device_printf(sc->dev, "can't get mac\n");
		return (ENXIO);
	}

	/* Enable MII clock */
	reg = (MDIO_CLK_DIV_DEFAULT << MDIO_SETUP_CLK_DIV_S);
	reg |= MDIO_SETUP_ENABLE;
	WRITE4(sc, XAE_MDIO_SETUP, reg);
	if (mdio_wait(sc))
		return (ENXIO);

	callout_init_mtx(&sc->xae_callout, &sc->mtx, 0);

	/* Setup interrupt handler. */
	error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_NET | INTR_MPSAFE,
	    NULL, xae_intr, sc, &sc->intr_cookie);
	if (error != 0) {
		device_printf(dev, "could not setup interrupt handler.\n");
		return (ENXIO);
	}

	/* Set up the ethernet interface. */
	sc->ifp = ifp = if_alloc(IFT_ETHER);
	if (ifp == NULL) {
		device_printf(dev, "could not allocate ifp.\n");
		return (ENXIO);
	}

	if_setsoftc(ifp, sc);
	if_initname(ifp, device_get_name(dev), device_get_unit(dev));
	if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
	if_setcapabilities(ifp, IFCAP_VLAN_MTU);
	if_setcapenable(ifp, if_getcapabilities(ifp));
	if_settransmitfn(ifp, xae_transmit);
	if_setqflushfn(ifp, xae_qflush);
	if_setioctlfn(ifp, xae_ioctl);
	if_setinitfn(ifp, xae_init);
	if_setsendqlen(ifp, TX_DESC_COUNT - 1);
	if_setsendqready(ifp);

	if (xae_get_phyaddr(node, &sc->phy_addr) != 0)
		return (ENXIO);

	/* Attach the mii driver. */
	error = mii_attach(dev, &sc->miibus, ifp, xae_media_change,
	    xae_media_status, BMSR_DEFCAPMASK, sc->phy_addr,
	    MII_OFFSET_ANY, 0);

	if (error != 0) {
		device_printf(dev, "PHY attach failed\n");
		return (ENXIO);
	}
	sc->mii_softc = device_get_softc(sc->miibus);

	/* Apply vcu118 workaround. */
	if (OF_getproplen(node, "xlnx,vcu118") >= 0)
		xae_phy_fixup(sc);

	/* All ready to run, attach the ethernet interface. */
	ether_ifattach(ifp, sc->macaddr);
	sc->is_attached = true;

	xae_rx_enqueue(sc, NUM_RX_MBUF);
	xdma_queue_submit(sc->xchan_rx);

	return (0);
}

static int
xae_detach(device_t dev)
{
	struct xae_softc *sc;
	if_t ifp;

	sc = device_get_softc(dev);

	KASSERT(mtx_initialized(&sc->mtx), ("%s: mutex not initialized",
	    device_get_nameunit(dev)));

	ifp = sc->ifp;

	/* Only cleanup if attach succeeded. */
	if (device_is_attached(dev)) {
		XAE_LOCK(sc);
		xae_stop_locked(sc);
		XAE_UNLOCK(sc);
		callout_drain(&sc->xae_callout);
		ether_ifdetach(ifp);
	}

	if (sc->miibus != NULL)
		device_delete_child(dev, sc->miibus);

	if (ifp != NULL)
		if_free(ifp);

	mtx_destroy(&sc->mtx);

	bus_teardown_intr(dev, sc->res[1], sc->intr_cookie);

	bus_release_resources(dev, xae_spec, sc->res);

	xdma_channel_free(sc->xchan_tx);
	xdma_channel_free(sc->xchan_rx);
	xdma_put(sc->xdma_tx);
	xdma_put(sc->xdma_rx);

	return (0);
}

static void
xae_miibus_statchg(device_t dev)
{
	struct xae_softc *sc;
	struct mii_data *mii;
	uint32_t reg;

	/*
	 * Called by the MII bus driver when the PHY establishes
	 * link to set the MAC interface registers.
	 */

	sc = device_get_softc(dev);

	XAE_ASSERT_LOCKED(sc);

	mii = sc->mii_softc;

	if (mii->mii_media_status & IFM_ACTIVE)
		sc->link_is_up = true;
	else
		sc->link_is_up = false;

	switch (IFM_SUBTYPE(mii->mii_media_active)) {
	case IFM_1000_T:
	case IFM_1000_SX:
		reg = SPEED_1000;
		break;
	case IFM_100_TX:
		reg = SPEED_100;
		break;
	case IFM_10_T:
		reg = SPEED_10;
		break;
	case IFM_NONE:
		sc->link_is_up = false;
		return;
	default:
		sc->link_is_up = false;
		device_printf(dev, "Unsupported media %u\n",
		    IFM_SUBTYPE(mii->mii_media_active));
		return;
	}

	WRITE4(sc, XAE_SPEED, reg);
}

static device_method_t xae_methods[] = {
	DEVMETHOD(device_probe,		xae_probe),
	DEVMETHOD(device_attach,	xae_attach),
	DEVMETHOD(device_detach,	xae_detach),

	/* MII Interface */
	DEVMETHOD(miibus_readreg,	xae_miibus_read_reg),
	DEVMETHOD(miibus_writereg,	xae_miibus_write_reg),
	DEVMETHOD(miibus_statchg,	xae_miibus_statchg),
	{ 0, 0 }
};

driver_t xae_driver = {
	"xae",
	xae_methods,
	sizeof(struct xae_softc),
};

DRIVER_MODULE(xae, simplebus, xae_driver, 0, 0);
DRIVER_MODULE(miibus, xae, miibus_driver, 0, 0);

MODULE_DEPEND(xae, ether, 1, 1, 1);
MODULE_DEPEND(xae, miibus, 1, 1, 1);