/* $NetBSD: if_eh.c,v 1.17 2012/05/11 15:39:18 skrll Exp $ */

/*-
 * Copyright (c) 2000 Ben Harris
 * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
 * NASA Ames Research Center.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */
/*
 * Copyright (c) 1994, 1995 Charles M. Hannum.  All rights reserved.
 *
 * Copyright (C) 1993, David Greenman.  This software may be used, modified,
 * copied, distributed, and sold, in both source and binary form provided that
 * the above copyright and these terms are retained.  Under no circumstances is
 * the author responsible for the proper functioning of this software, nor does
 * the author assume any responsibility for damages incurred with its use.
 */
/*
 * if_eh.c -- driver for i-cubed EtherLan 100-, 200- and 500-series cards.
 */

#include <sys/param.h>

__KERNEL_RCSID(0, "$NetBSD: if_eh.c,v 1.17 2012/05/11 15:39:18 skrll Exp $");

#include <sys/systm.h>
#include <sys/device.h>
#include <sys/socket.h>
#include <sys/mbuf.h>
#include <sys/reboot.h>
#include <sys/syslog.h>
#include <sys/bus.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_media.h>

#include <net/if_ether.h>

#include <sys/bswap.h>
#include <machine/irq.h>

#ifndef __BUS_SPACE_HAS_STREAM_METHODS
#define	bus_space_write_stream_2	bus_space_write_2
#define	bus_space_write_multi_stream_2	bus_space_write_multi_2
#define	bus_space_read_multi_stream_2	bus_space_read_multi_2
#endif /* __BUS_SPACE_HAS_STREAM_METHODS */

#include <dev/ic/dp8390reg.h>
#include <dev/ic/dp8390var.h>

#include <dev/podulebus/podulebus.h>
#include <dev/podulebus/podules.h>
#include <arch/acorn26/podulebus/if_ehreg.h>

#define EH_MEDIA_2	0
#define EH_MEDIA_T	1
#define EH_MEDIA_2_T	2
#define EH_MEDIA_FL_T	3

struct eh_softc {
	struct	dp8390_softc sc_dp;
	bus_space_tag_t		sc_datat;
	bus_space_handle_t	sc_datah;
	bus_space_tag_t		sc_ctlt;
	bus_space_handle_t	sc_ctlh;
	bus_space_tag_t		sc_ctl2t;
	bus_space_handle_t	sc_ctl2h;
	void			*sc_ih;
	struct		evcnt	sc_intrcnt;
	int			sc_flags;
#define EHF_16BIT	0x01
#define EHF_MAU		0x02
	int			sc_type;
	int			sc_mediaset;
	uint8_t			sc_ctrl; /* Current control reg state */
};

int	eh_write_mbuf(struct dp8390_softc *, struct mbuf *, int);
int	eh_ring_copy(struct dp8390_softc *, int, void *, u_short);
void	eh_read_hdr(struct dp8390_softc *, int, struct dp8390_ring *);
int	eh_test_mem(struct dp8390_softc *);

void	eh_writemem(struct eh_softc *, uint8_t *, int, size_t);
void	eh_readmem(struct eh_softc *, int, uint8_t *, size_t);
static void eh_init_card(struct dp8390_softc *);
static int eh_availmedia(struct eh_softc *);
/*static*/ int eh_identifymau(struct eh_softc *);

void	eh_media_init(struct dp8390_softc *);

/* if_media glue */
static int eh_mediachange(struct dp8390_softc *);
static void eh_mediastatus(struct dp8390_softc *, struct ifmediareq *);

/* autoconfiguration glue */
static int eh_match(device_t, cfdata_t , void *);
static void eh_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(eh, sizeof(struct eh_softc),
    eh_match, eh_attach, NULL, NULL);

static int
eh_match(device_t parent, cfdata_t cf, void *aux)
{
	struct podulebus_attach_args *pa = aux;

	switch (pa->pa_product) {
	case PODULE_ETHERLAN100:
	case PODULE_ETHERLAN100AEH:
	case PODULE_ETHERLAN200:
	case PODULE_ETHERLAN200AEH:
	case PODULE_ETHERLAN500:
	case PODULE_ETHERLAN500AEH:
		return 1;
	}
	return 0;
}

/* XXX 10BASE-FL on E513 */
static int media_only2[] = { IFM_ETHER | IFM_10_2 };
static int media_onlyt[] = { IFM_ETHER | IFM_10_T };
static int media_2andt[] =
    { IFM_ETHER | IFM_AUTO, IFM_ETHER | IFM_10_2, IFM_ETHER | IFM_10_T };
static const struct {
	int nmedia;
	int *media;
} media_switch[] = {
	{ 1, media_only2 },
	{ 1, media_onlyt },
	{ 3, media_2andt }
};

static void
eh_attach(device_t parent, device_t self, void *aux)
{
	struct podulebus_attach_args *pa = aux;
	struct eh_softc *sc = device_private(self);
	struct dp8390_softc *dsc = &sc->sc_dp;
	int mediaset, mautype;
	int i;
	char *ptr;
	uint8_t *myaddr;

	dsc->sc_dev = self;

	/* Canonicalise card type. */
	switch (pa->pa_product) {
	case PODULE_ETHERLAN100:
	case PODULE_ETHERLAN100AEH:
		sc->sc_type = PODULE_ETHERLAN100;
		break;
	case PODULE_ETHERLAN200:
	case PODULE_ETHERLAN200AEH:
		sc->sc_type = PODULE_ETHERLAN200;
		break;
	case PODULE_ETHERLAN500:
	case PODULE_ETHERLAN500AEH:
		sc->sc_type = PODULE_ETHERLAN500;
		break;
	}

	/* Memory size and width varies. */
	dsc->mem_start = 0;
	switch (sc->sc_type) {
	case PODULE_ETHERLAN200:
		sc->sc_flags |= EHF_MAU;
		/* FALLTHROUGH */
	case PODULE_ETHERLAN100:
		printf(": 8-bit, 32 KB RAM");
		dsc->mem_size  = 0x8000;
		break;
	case PODULE_ETHERLAN500:
		printf(": 16-bit, 64 KB RAM");
		sc->sc_flags |= EHF_16BIT;
		dsc->mem_size = 0x10000;
		break;
	}

	/* Set up bus spaces */
	dsc->sc_regt = pa->pa_mod_t;
	bus_space_subregion(dsc->sc_regt, pa->pa_mod_h, EH_DP8390, 0x10,
	    &dsc->sc_regh);
	sc->sc_datat = pa->pa_mod_t;
	bus_space_subregion(sc->sc_datat, pa->pa_mod_h, EH_DATA, 1,
	    &sc->sc_datah);
	sc->sc_ctlt = pa->pa_fast_t;
	bus_space_subregion(sc->sc_ctlt, pa->pa_fast_h, EH_CTRL, 1,
	    &sc->sc_ctlh);
	sc->sc_ctl2t = pa->pa_fast_t;
	bus_space_subregion(sc->sc_ctl2t, pa->pa_fast_h, EH_CTRL2, 1,
	    &sc->sc_ctl2h);

	/* dsc->cr_proto? */
	/* dsc->rcr_proto? */

	/* Follow NE2000 driver here. */
	dsc->dcr_reg = ED_DCR_FT1 | ED_DCR_LS;
	if (sc->sc_flags & EHF_16BIT)
		dsc->dcr_reg |= ED_DCR_WTS;

	/* Set up callbacks */
	dsc->test_mem = eh_test_mem;
	dsc->init_card = eh_init_card;
	dsc->read_hdr = eh_read_hdr;
	/* dsc->recv_int */
	dsc->ring_copy = eh_ring_copy;
	dsc->write_mbuf = eh_write_mbuf;
	/* dsc->sc_enable */
	/* dsc->sc_disable */
	dsc->sc_mediachange = eh_mediachange;
	dsc->sc_mediastatus = eh_mediastatus;
	dsc->sc_media_init = eh_media_init;
	/* dsc->sc_media_fini */

	for (i = 0; i < 16; i++)
		dsc->sc_reg_map[i] = i;

	sc->sc_ctrl = 0x00;

	if (sc->sc_flags & EHF_MAU) {
		mautype = eh_identifymau(sc);
		switch (mautype) {
		case EH200_MAUID_10_2:
			printf(", 10BASE2 MAU");
			mediaset = EH_MEDIA_2;
			break;
		case EH200_MAUID_10_T:
			printf(", 10BASE-T MAU");
			mediaset = EH_MEDIA_T;
			break;
		default:
			printf(", unknown MAU id %d", mautype);
			mediaset = EH_MEDIA_2; /* XXX */
			break;
		}
	} else {
		mediaset = eh_availmedia(sc);
		switch (mediaset) {
		case EH_MEDIA_2:
			printf(", 10BASE2 only");
			break;
		case EH_MEDIA_T:
			printf(", 10BASE-T only");
			break;
		case EH_MEDIA_2_T:
			printf(", combo 10BASE2/-T");
			break;
		}
	}
	sc->sc_mediaset = mediaset;
	printf("\n");

	/* i-cubed put everything behind the loader. */
	podulebus_initloader(pa);

	/*
	 * Get the Ethernet address from the device description string.
	 * This code is stolen from if_ea.c.  It should be shared.
	 */
	myaddr = dsc->sc_enaddr;
	if (pa->pa_descr == NULL) {
		printf(": No description for Ethernet address\n");
		return;
	}
	ptr = strchr(pa->pa_descr, '(');
	if (ptr == NULL) {
		printf(": Ethernet address not found in description\n");
		return;
	}
	ptr++;
	for (i = 0; i < ETHER_ADDR_LEN; i++) {
		myaddr[i] = strtoul(ptr, &ptr, 16);
		if (*ptr++ != (i == ETHER_ADDR_LEN - 1 ? ')' : ':')) {
			printf(": Bad Ethernet address found in "
			       "description\n");
			return;
		}
	}

	dp8390_config(dsc);
	dp8390_stop(dsc);

	evcnt_attach_dynamic(&sc->sc_intrcnt, EVCNT_TYPE_INTR, NULL,
	    device_xname(self), "intr");
	sc->sc_ih = podulebus_irq_establish(pa->pa_ih, IPL_NET, dp8390_intr,
	    &sc->sc_dp, &sc->sc_intrcnt);
	if (bootverbose)
		aprint_verbose_dev(self, "interrupting at %s\n",
		    irq_string(sc->sc_ih));
	sc->sc_ctrl |= EH_CTRL_IE;
	bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0, sc->sc_ctrl);
}

static void
eh_init_card(struct dp8390_softc *sc)
{

	eh_mediachange(sc);
}

/*
 * Write an mbuf chain to the destination NIC memory address using programmed
 * I/O.
 */
int
eh_write_mbuf(struct dp8390_softc *dsc, struct mbuf *m, int buf)
{
	struct eh_softc *sc = (struct eh_softc *)dsc;
	bus_space_tag_t nict = dsc->sc_regt;
	bus_space_handle_t nich = dsc->sc_regh;
	bus_space_tag_t datat = sc->sc_datat;
	bus_space_handle_t datah = sc->sc_datah;
	int savelen, padlen;
	int maxwait = 100;	/* about 120us */

	savelen = m->m_pkthdr.len;
	if (savelen < ETHER_MIN_LEN - ETHER_CRC_LEN) {
		padlen = ETHER_MIN_LEN - ETHER_CRC_LEN - savelen;
		savelen = ETHER_MIN_LEN - ETHER_CRC_LEN;
	} else
		padlen = 0;

	/*
	 * Set-up procedure per DP83902A data sheet:
	 *   I) Write a non-zero value into RBCR0.
	 *  II) Set bits RD2, RD1, and RD0 to 0, 0, and 1.
	 * III) Set RBCR0, 1 and RSAR0, 1.
	 *  IV) Issue the Remote Write DMA Command (RD2, RD1, RD0 = 0, 1, 0).
	 */

	/* Select page 0 registers. */

	NIC_BARRIER(nict, nich);
	bus_space_write_1(nict, nich, ED_P0_CR,
	    ED_CR_RD2 | ED_CR_PAGE_0 | ED_CR_STA);
	NIC_BARRIER(nict, nich);

	/* Write random number.  Linux writes several, follow them. */
	bus_space_write_1(nict, nich, ED_P0_RBCR0, 123);
	bus_space_write_1(nict, nich, ED_P0_RBCR1, 123);
	bus_space_write_1(nict, nich, ED_P0_RSAR0, 123);
	bus_space_write_1(nict, nich, ED_P0_RSAR1, 123);
	NIC_BARRIER(nict, nich);

	/* Linux has a 1us pause here.  Follow them. */
	DELAY(1);

	/* Select DMA read (see above) */
	bus_space_write_1(nict, nich, ED_P0_CR,
	    ED_CR_RD0 | ED_CR_PAGE_0 | ED_CR_STA);
	NIC_BARRIER(nict, nich);

	/* Reset remote DMA complete flag. */
	bus_space_write_1(nict, nich, ED_P0_ISR, ED_ISR_RDC);
	NIC_BARRIER(nict, nich);

	/* Set up DMA byte count. */
	bus_space_write_1(nict, nich, ED_P0_RBCR0, savelen);
	bus_space_write_1(nict, nich, ED_P0_RBCR1, savelen >> 8);

	/* Set up destination address in NIC mem. */
	bus_space_write_1(nict, nich, ED_P0_RSAR0, buf);
	bus_space_write_1(nict, nich, ED_P0_RSAR1, buf >> 8);

	/* Set remote DMA write. */
	NIC_BARRIER(nict, nich);
	bus_space_write_1(nict, nich,
	    ED_P0_CR, ED_CR_RD1 | ED_CR_PAGE_0 | ED_CR_STA);
	NIC_BARRIER(nict, nich);

	/*
	 * Transfer the mbuf chain to the NIC memory.  NE2000 cards
	 * require that data be transferred as words, and only words,
	 * so that case requires some extra code to patch over odd-length
	 * mbufs.
	 */
	if ((sc->sc_flags & EHF_16BIT) == 0) {
		/* NE1000s are easy. */
		for (; m != 0; m = m->m_next)
			if (m->m_len)
				bus_space_write_multi_1(datat, datah, 0,
				    mtod(m, uint8_t *), m->m_len);
		if (padlen) {
			for(; padlen > 0; padlen--)
				bus_space_write_1(datat, datah, 0, 0);
		}
	} else {
		/* NE2000s are a bit trickier. */
		uint8_t *data, savebyte[2];
		int l, leftover;
#ifdef DIAGNOSTIC
		uint8_t *lim;
#endif
		/* Start out with no leftover data. */
		leftover = 0;
		savebyte[0] = savebyte[1] = 0;

		for (; m != 0; m = m->m_next) {
			l = m->m_len;
			if (l == 0)
				continue;
			data = mtod(m, uint8_t *);
#ifdef DIAGNOSTIC
			lim = data + l;
#endif
			while (l > 0) {
				if (leftover) {
					/*
					 * Data left over (from mbuf or
					 * realignment).  Buffer the next
					 * byte, and write it and the
					 * leftover data out.
					 */
					savebyte[1] = *data++;
					l--;
					bus_space_write_stream_2(datat, datah,
					    0,
					    *(uint16_t *)savebyte);
					leftover = 0;
				} else if (BUS_SPACE_ALIGNED_POINTER(data,
					   uint16_t) == 0) {
					/*
					 * Unaligned data; buffer the next
					 * byte.
					 */
					savebyte[0] = *data++;
					l--;
					leftover = 1;
				} else {
					/*
					 * Aligned data; output contiguous
					 * words as much as we can, then
					 * buffer the remaining byte, if any.
					 */
					leftover = l & 1;
					l &= ~1;
					bus_space_write_multi_stream_2(datat,
					    datah, 0,
					    (uint16_t *)data, l >> 1);
					data += l;
					if (leftover)
						savebyte[0] = *data++;
					l = 0;
				}
			}
			if (l < 0)
				panic("eh_write_mbuf: negative len");
#ifdef DIAGNOSTIC
			if (data != lim)
				panic("eh_write_mbuf: data != lim");
#endif
		}
		if (leftover) {
			savebyte[1] = 0;
			bus_space_write_stream_2(datat, datah, 0,
			    *(uint16_t *)savebyte);
		}
		if (padlen) {
			for(; padlen > 0; padlen -= 2)
				bus_space_write_stream_2(datat, datah, 0, 0);
		}
	}
	NIC_BARRIER(nict, nich);

	/*
	 * Wait for remote DMA to complete.  This is necessary because on the
	 * transmit side, data is handled internally by the NIC in bursts, and
	 * we can't start another remote DMA until this one completes.  Not
	 * waiting causes really bad things to happen - like the NIC wedging
	 * the bus.
	 */
	while (((bus_space_read_1(nict, nich, ED_P0_ISR) & ED_ISR_RDC) !=
	    ED_ISR_RDC) && --maxwait) {
		bus_space_read_1(nict, nich, ED_P0_CRDA1);
		bus_space_read_1(nict, nich, ED_P0_CRDA0);
		NIC_BARRIER(nict, nich);
		DELAY(1);
	}

	if (maxwait == 0) {
		log(LOG_WARNING,
		    "%s: remote transmit DMA failed to complete "
		    "(RSAR=0x%04x, RBCR=0x%04x, CRDA=0x%02x%02x)\n",
		    device_xname(dsc->sc_dev), buf, savelen,
		    bus_space_read_1(nict, nich, ED_P0_CRDA1),
		    bus_space_read_1(nict, nich, ED_P0_CRDA0));
		dp8390_reset(dsc);
	}

	return (savelen);
}

/*
 * Given a source and destination address, copy 'amout' of a packet from
 * the ring buffer into a linear destination buffer.  Takes into account
 * ring-wrap.
 */
int
eh_ring_copy(struct dp8390_softc *dsc, int src, void *dst_arg, u_short amount)
{
	struct eh_softc *sc = (struct eh_softc *)dsc;
	u_short tmp_amount;
	uint8_t *dst = dst_arg;

	/* Does copy wrap to lower addr in ring buffer? */
	if (src + amount > dsc->mem_end) {
		tmp_amount = dsc->mem_end - src;

		/* Copy amount up to end of NIC memory. */
		eh_readmem(sc, src, dst, tmp_amount);

		amount -= tmp_amount;
		src = dsc->mem_ring;
		dst += tmp_amount;
	}

	eh_readmem(sc, src, dst, amount);

	return (src + amount);
}

void
eh_read_hdr(struct dp8390_softc *dsc, int buf, struct dp8390_ring *hdr)
{
	struct eh_softc *sc = (struct eh_softc *)dsc;

	eh_readmem(sc, buf, (uint8_t *)hdr, sizeof(struct dp8390_ring));
#if BYTE_ORDER == BIG_ENDIAN
	hdr->count = bswap16(hdr->count);
#endif
}

int
eh_test_mem(struct dp8390_softc *dsc)
{
	struct eh_softc *sc = (struct eh_softc *)dsc;
	uint8_t block[256];
	int ptr, mem_end;

	mem_end = dsc->mem_start + dsc->mem_size;
	memset(block, 0, 256);
	for (ptr = dsc->mem_start; ptr < mem_end; ptr+=256)
		eh_writemem(sc, block, ptr, 256);
	return 0;
}

/*
 * Given a NIC memory source address and a host memory destination address,
 * copy 'amount' from NIC to host using programmed i/o.  The 'amount' is
 * rounded up to a word - ok as long as mbufs are word sized.
 */
void
eh_readmem(struct eh_softc *sc, int src, uint8_t *dst, size_t amount)
{
	bus_space_tag_t nict = sc->sc_dp.sc_regt;
	bus_space_handle_t nich = sc->sc_dp.sc_regh;
	bus_space_tag_t datat = sc->sc_datat;
	bus_space_handle_t datah = sc->sc_datah;

	/* Select page 0 registers. */
	NIC_BARRIER(nict, nich);
	bus_space_write_1(nict, nich, ED_P0_CR,
	    ED_CR_RD2 | ED_CR_PAGE_0 | ED_CR_STA);
	NIC_BARRIER(nict, nich);

	/* Round up to a word if necessary. */
	if ((sc->sc_flags & EHF_16BIT) && (amount & 1))
		++amount;

	/* Set up DMA byte count. */
	bus_space_write_1(nict, nich, ED_P0_RBCR0, amount);
	bus_space_write_1(nict, nich, ED_P0_RBCR1, amount >> 8);

	/* Set up source address in NIC mem. */
	bus_space_write_1(nict, nich, ED_P0_RSAR0, src);
	bus_space_write_1(nict, nich, ED_P0_RSAR1, src >> 8);

	NIC_BARRIER(nict, nich);
	bus_space_write_1(nict, nich, ED_P0_CR,
	    ED_CR_RD0 | ED_CR_PAGE_0 | ED_CR_STA);
	NIC_BARRIER(nict, nich);

	if (sc->sc_flags & EHF_16BIT) {
#ifdef DIAGNOSTIC
		if (!ALIGNED_POINTER(dst, uint16_t))
			panic("eh_readmem");
#endif
		bus_space_read_multi_stream_2(datat, datah, 0,
		    (uint16_t *)dst, amount >> 1);
	} else
		bus_space_read_multi_1(datat, datah, 0, dst, amount);
}

void
eh_writemem(struct eh_softc *sc, uint8_t *src, int dst, size_t len)
{
	bus_space_tag_t nict = sc->sc_dp.sc_regt;
	bus_space_handle_t nich = sc->sc_dp.sc_regh;
	bus_space_tag_t datat = sc->sc_datat;
	bus_space_handle_t datah = sc->sc_datah;
	int maxwait = 100;	/* about 120us */

	/*
	 * Set-up procedure per DP83902A data sheet:
	 *   I) Write a non-zero value into RBCR0.
	 *  II) Set bits RD2, RD1, and RD0 to 0, 0, and 1.
	 * III) Set RBCR0, 1 and RSAR0, 1.
	 *  IV) Issue the Remote Write DMA Command (RD2, RD1, RD0 = 0, 1, 0).
	 */

	/* Select page 0 registers. */

	NIC_BARRIER(nict, nich);
	bus_space_write_1(nict, nich, ED_P0_CR,
	    ED_CR_RD2 | ED_CR_PAGE_0 | ED_CR_STA);
	NIC_BARRIER(nict, nich);

	/* Write random number.  Linux writes several, follow them. */
	bus_space_write_1(nict, nich, ED_P0_RBCR0, 123);
	bus_space_write_1(nict, nich, ED_P0_RBCR1, 123);
	bus_space_write_1(nict, nich, ED_P0_RSAR0, 123);
	bus_space_write_1(nict, nich, ED_P0_RSAR1, 123);
	NIC_BARRIER(nict, nich);

	/* Select DMA read (see above) */
	bus_space_write_1(nict, nich, ED_P0_CR,
	    ED_CR_RD0 | ED_CR_PAGE_0 | ED_CR_STA);
	NIC_BARRIER(nict, nich);

	/* Linux has a 1us pause here.  Follow them. */
	DELAY(1);

	/* Reset remote DMA complete flag. */
	bus_space_write_1(nict, nich, ED_P0_ISR, ED_ISR_RDC);
	NIC_BARRIER(nict, nich);

	/* Set up DMA byte count. */
	bus_space_write_1(nict, nich, ED_P0_RBCR0, len);
	bus_space_write_1(nict, nich, ED_P0_RBCR1, len >> 8);

	/* Set up destination address in NIC mem. */
	bus_space_write_1(nict, nich, ED_P0_RSAR0, dst);
	bus_space_write_1(nict, nich, ED_P0_RSAR1, dst >> 8);

	/* Set remote DMA write. */
	NIC_BARRIER(nict, nich);
	bus_space_write_1(nict, nich,
	    ED_P0_CR, ED_CR_RD1 | ED_CR_PAGE_0 | ED_CR_STA);
	NIC_BARRIER(nict, nich);

	if (sc->sc_flags & EHF_16BIT) {
#ifdef DIAGNOSTIC
		if (!ALIGNED_POINTER(dst, uint16_t))
			panic("eh_writemem");
#endif
		bus_space_write_multi_stream_2(datat, datah, 0,
		    (uint16_t *)src, len >> 1);
	} else
		bus_space_write_multi_1(datat, datah, 0, src, len);

	/*
	 * Wait for remote DMA to complete.  This is necessary because on the
	 * transmit side, data is handled internally by the NIC in bursts, and
	 * we can't start another remote DMA until this one completes.  Not
	 * waiting causes really bad things to happen - like the NIC wedging
	 * the bus.
	 */
	while (((bus_space_read_1(nict, nich, ED_P0_ISR) & ED_ISR_RDC) !=
	    ED_ISR_RDC) && --maxwait)
		DELAY(1);

	if (maxwait == 0)
		printf("eh_writemem: failed to complete "
		    "(RSAR=0x%04x, RBCR=0x%04x, CRDA=0x%02x%02x)\n",
		    dst, (u_int)len,
		    bus_space_read_1(nict, nich, ED_P0_CRDA1),
		    bus_space_read_1(nict, nich, ED_P0_CRDA0));
}

/*
 * Work out the media types available on the current card.
 *
 * We try to switch to each of 10BASE2 and 10BASE-T in turn.  If the card
 * only supports one type, the media select line will be tied to select
 * that, so it won't move when we push it.
 *
 * The media select jumpers (at least on the EtherLan 100) have the same
 * effect.
 */

int
eh_availmedia(struct eh_softc *sc)
{

	/* Set the card to use AUI (10BASE2 or 10BASE-FL) */
	bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0,
	    sc->sc_ctrl & ~EH_CTRL_MEDIA);
	/* Check whether that worked */
	if ((bus_space_read_1(sc->sc_ctl2t, sc->sc_ctl2h, 0) &
	    EH_CTRL2_10B2) == 0) {
		bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0, sc->sc_ctrl);
		return EH_MEDIA_T;
	}

	/* Try 10BASE-T and see if that works */
	bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0,
	    sc->sc_ctrl | EH_CTRL_MEDIA);
	if ((bus_space_read_1(sc->sc_ctl2t, sc->sc_ctl2h, 0) &
	    EH_CTRL2_10B2)) {
		bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0, sc->sc_ctrl);
		return EH_MEDIA_2;
	}

	/* If both of them worked, this is a combo card. */
	bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0, sc->sc_ctrl);
	return EH_MEDIA_2_T;
}

/*
 * Identify the MAU attached to an EtherLan 200-series network slot card.
 *
 * Follows the protocol described at the back of the Acorn A3020 and A4000
 * Network Interface Specification.
 */
int
eh_identifymau(struct eh_softc *sc)
{
	int id;
	bus_space_tag_t ctlt;
	bus_space_handle_t ctlh;

	ctlt = sc->sc_ctlt;
	ctlh = sc->sc_ctlh;
	/* Reset: Output 1 for a nominal 100us. */
	/* XXX For some reason, a read is necessary between writes. */
	bus_space_read_1(ctlt, ctlh, 0);
	bus_space_write_1(ctlt, ctlh, 0, EH200_CTRL_MAU);
	DELAY(200000);
	for (id = 0; id < 128; id++) {
		/* Output 0 for 10us. */
		/* XXX For some reason, a read is necessary between writes. */
		bus_space_read_1(ctlt, ctlh, 0);
		bus_space_write_1(ctlt, ctlh, 0, 0);
		DELAY(10);
		/* Read state. */
		if (bus_space_read_1(ctlt, ctlh, 0) & EH200_CTRL_MAU)
			break;
		/* Output 1 for 10us. */
		bus_space_write_1(ctlt, ctlh, 0, EH200_CTRL_MAU);
		DELAY(10);
	}
	return id;
}

void
eh_media_init(struct dp8390_softc *dsc)
{
	struct eh_softc *sc = (struct eh_softc *) dsc;
	int i;

	ifmedia_init(&dsc->sc_media, 0, dp8390_mediachange, dp8390_mediastatus);
	for (i = 0; i < media_switch[sc->sc_mediaset].nmedia; i++)
		ifmedia_add(&dsc->sc_media,
		    media_switch[sc->sc_mediaset].media[i], 0, NULL);
	ifmedia_set(&dsc->sc_media, media_switch[sc->sc_mediaset].media[0]);
}

/*
 * Medium selection has changed.
 */
int
eh_mediachange(struct dp8390_softc *dsc)
{
	struct eh_softc *sc = (struct eh_softc *)dsc;
	struct ifmedia *ifm = &dsc->sc_media;

	switch (IFM_SUBTYPE(ifm->ifm_cur->ifm_media)) {
	case IFM_AUTO:
		/* Auto-media logic from Linux */
		sc->sc_ctrl |= EH_CTRL_MEDIA;
		bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0, sc->sc_ctrl);
		DELAY(1000); /* XXX Long enough for hub to respond? */
		if ((bus_space_read_1(sc->sc_ctlt, sc->sc_ctlh, 0) &
		    EH_CTRL_NOLINK) == 0)
			break;
		/* FALLTHROUGH */
	case IFM_10_2:
		sc->sc_ctrl &= ~EH_CTRL_MEDIA;
		bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0, sc->sc_ctrl);
		break;
	case IFM_10_T:
		sc->sc_ctrl |= EH_CTRL_MEDIA;
		bus_space_write_1(sc->sc_ctlt, sc->sc_ctlh, 0, sc->sc_ctrl);
		break;
#ifdef DIAGNOSTIC
	default:
		panic("eh_mediachange");
#endif
	}
	return 0;
}

/*
 * Return current medium and status.
 */
void
eh_mediastatus(struct dp8390_softc *dsc, struct ifmediareq *ifmr)
{
	struct eh_softc *sc = (struct eh_softc *)dsc;
	int ctrl2;

	/* XXX 10BASE-FL on E513? */
	/* Read the actual medium currently in use. */
	ctrl2 = bus_space_read_1(sc->sc_ctl2t, sc->sc_ctl2h, 0);
	if (ctrl2 & EH_CTRL2_10B2) {
		ifmr->ifm_active = IFM_ETHER | IFM_10_2;
	} else {
		ifmr->ifm_active = IFM_ETHER | IFM_10_T;
		ifmr->ifm_status = IFM_AVALID;
		if ((bus_space_read_1(sc->sc_ctlt, sc->sc_ctlh, 0) &
		    EH_CTRL_NOLINK) == 0)
			ifmr->ifm_status |= IFM_ACTIVE;
	}

}
