/*-
 * Copyright (c) 2003 Matthew N. Dodd
 * 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 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.
 *
 * Portions: 
 * Copyright (c) 1992, 1993, University of Vermont and State
 *  Agricultural College.
 * Copyright (c) 1992, 1993, Garrett A. Wollman.
 * Copyright (c) 1990, 1991, William F. Jolitz
 * Copyright (c) 1990, The Regents of the University of California
 * Copyright (c) 1993, 1994, Charles M. Hannum
 * Copyright (c) 1993, 1994, 1995, Rodney W. Grimes
 * Copyright (c) 1997, Aaron C. Smith
 *
 * See if_ie.c for applicable license.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/dev/ie/if_ie_isa.c,v 1.6 2006/05/16 14:36:26 phk Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/socket.h>

#include <sys/module.h>
#include <sys/bus.h>

#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>

#include <machine/md_var.h>

#include <net/if.h>
#include <net/if_arp.h>
#include <net/if_media.h>

#include <isa/isavar.h>
#include <isa/pnpvar.h>

#include <i386/isa/elink.h>

#include <dev/ic/i82586.h>
#include <dev/ie/if_ie507.h>
#include <dev/ie/if_iee16.h>
#include <dev/ie/if_iereg.h>
#include <dev/ie/if_ievar.h>

static int		ie_modevent		(module_t, int, void *);

static void		ie_isa_3C507_identify	(driver_t *, device_t);
static int		ie_isa_3C507_probe	(device_t);
static int		ie_isa_3C507_attach	(device_t);
static int		ie_3C507_port_check	(u_int32_t);

static void		ie_isa_ee16_identify	(driver_t *, device_t);
static int		ie_isa_ee16_probe	(device_t);
static int		ie_isa_ee16_attach	(device_t);
static int		ie_ee16_port_check	(u_int32_t port);
static u_int16_t	ie_ee16_hw_read_eeprom	(u_int32_t port, int loc);

static int		ie_isa_sl_probe		(device_t);
static int		ie_isa_sl_attach	(device_t);
static enum ie_hardware	ie_isa_sl_get_hard_type	(u_int32_t);

/*
 * 3Com 3C507 Etherlink 16
 */
#define IE_3C507_IOBASE_LOW	0x200
#define IE_3C507_IOBASE_HIGH	0x3e0
#define IE_3C507_IOSIZE		16

#define IE_3C507_IRQ_MASK	0x0f

#define IE_3C507_MADDR_HIGH	0x20
#define IE_3C507_MADDR_MASK	0x1c
#define IE_3C507_MADDR_BASE	0xc0000
#define IE_3C507_MADDR_SHIFT	12

#define IE_3C507_MSIZE_MASK	3
#define IE_3C507_MSIZE_SHIFT	14

static void
ie_isa_3C507_identify (driver_t *driver, device_t parent)
{
	char *		desc = "3Com 3C507 Etherlink 16";
	device_t	child;
	u_int32_t	port, maddr, msize;
	u_int8_t	irq, data;
	int		error;

	/* Reset and put card in CONFIG state without changing address. */
	elink_reset();
	elink_idseq(ELINK_507_POLY);
	elink_idseq(ELINK_507_POLY);
	outb(ELINK_ID_PORT, 0xff);

	for (port = IE_3C507_IOBASE_LOW;
	     port <= IE_3C507_IOBASE_HIGH;
	     port += IE_3C507_IOSIZE) {

		if (ie_3C507_port_check(port)) {
#ifdef DEBUG 
			if (bootverbose) {
				device_printf(parent,
					"(if_ie) (3C507) not found at port %#x\n",
					port);
			}
#endif
			continue;
		}

		outb(port + IE507_CTRL, EL_CTRL_NRST);

		data = inb(port + IE507_IRQ);
		irq = data & IE_3C507_IRQ_MASK;

		data = inb(port + IE507_MADDR);

		if (data & IE_3C507_MADDR_HIGH) {
			if (bootverbose) {
				device_printf(parent,
					"(if_ie) can't map 3C507 RAM in high memory\n");
			}
			continue;
		}

		maddr = IE_3C507_MADDR_BASE +
			((data & IE_3C507_MADDR_MASK)
			<< IE_3C507_MADDR_SHIFT);
		msize = ((data & IE_3C507_MSIZE_MASK) + 1)
			<< IE_3C507_MSIZE_SHIFT;

		child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "ie", -1);
		device_set_desc_copy(child, desc);
		device_set_driver(child, driver);

		error = bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1);
		if (error) {
			device_printf(parent, "(if_ie) Unable to set IRQ resource %d.\n",
					irq);
			error = device_delete_child(parent, child);
			continue;
		}

		error = bus_set_resource(child, SYS_RES_IOPORT, 0, port, IE_3C507_IOSIZE);
		if (error) {
			device_printf(parent, "(if_ie) Unable to set IOPORT resource %#x-%#x.\n",
					port, port+IE_3C507_IOSIZE);
			error = device_delete_child(parent, child);
			continue;
		}

		error = bus_set_resource(child, SYS_RES_MEMORY, 0, maddr, msize);
		if (error) {
			device_printf(parent, "(if_ie) Unable to set MEMORY resource %#x-%#x.\n",
					maddr, maddr+msize);
			error = device_delete_child(parent, child);
			continue;
		}

		if (bootverbose) {
			device_printf(parent,
				"(if_ie) <%s> at port %#x-%#x irq %d iomem %#lx-%#lx (%dKB)\n",
				desc,
				port, (port + IE_3C507_IOSIZE) - 1,
				irq,
				(u_long)maddr, (u_long)(maddr + msize) - 1,
				(msize / 1024));
		}
	}

	/* go to RUN state */
	outb(ELINK_ID_PORT, 0x00);
	elink_idseq(ELINK_507_POLY);
	outb(ELINK_ID_PORT, 0x00);

	return;
}

static int
ie_isa_3C507_probe (device_t dev)
{
	u_int32_t	iobase;

	/* No ISA-PnP support */
	if (isa_get_vendorid(dev)) {
		return (ENXIO);
	}

	/* No ISA-HINT support */
	if (!device_get_desc(dev)) {
		return (EBUSY);
	}

	/* Have we at least an ioport? */
	if ((iobase = bus_get_resource_start(dev, SYS_RES_IOPORT, 0)) == 0) {
		return (ENXIO);
	}

	/* Is this thing really a 3c507? */
	if (ie_3C507_port_check(iobase)) {
		return (ENXIO);
	}

	return (0);
}

static int
ie_isa_3C507_attach (device_t dev)
{
	struct ie_softc *	sc;
	int			error;

	sc = device_get_softc(dev);

	sc->io_rid = 0;
	sc->irq_rid = 0;
	sc->mem_rid = 0;

	error = ie_alloc_resources(dev);
	if (error) {
		goto bad;
	}

	sc->bus_use = 0;
	sc->ie_reset_586 = el_reset_586;
	sc->ie_chan_attn = el_chan_attn;
	sc->hard_type = IE_3C507;
	sc->hard_vers = 0;

	outb(PORT(sc) + IE507_CTRL, EL_CTRL_NORMAL);

	if (!check_ie_present(sc)) {
		error = ENXIO;
		goto bad;
	}

	sl_read_ether(sc, sc->enaddr);

	/* Clear the interrupt latch just in case. */
	outb(PORT(sc) + IE507_ICTRL, 1);

	error = ie_attach(dev);
	if (error) {
		device_printf(dev, "ie_attach() failed.\n");
		goto bad;
	}

	error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET,
				ie_intr, sc, &sc->irq_ih);
	if (error) {
		device_printf(dev, "Unable to register interrupt handler\n"); 
		goto bad;
	}

	return (0);
bad:
	ie_release_resources(dev);

	return (error);
}

/*
 * If a 3c507 is present, return 0
 * else, return 1.
 */
static int
ie_3C507_port_check (u_int32_t port)
{
	u_char *	signature = "*3COM*";
	int		i;

	for (i = 0; i < 6; i++)
		if (inb(port + i) != signature[i])
			return (ENXIO);

	return (0);
}

/*
 * Intel EtherExpress 16
 */
#define IE_EE16_ID_PORT			0x0f
#define IE_EE16_ID			0xbaba
#define IE_EE16_EEPROM_CONFIG1		0x00
#define IE_EE16_EEPROM_IRQ_MASK		0xe000
#define IE_EE16_EEPROM_IRQ_SHIFT	13
#define IE_EE16_EEPROM_MEMCFG		0x06
#define IE_EE16_IOSIZE			16

/*
 * TODO:
 *		Test for 8/16 bit mode.
 *		Test for invalid mem sizes.
 */
static void
ie_isa_ee16_identify (driver_t *driver, device_t parent)
{
	char *		desc = "Intel EtherExpress 16";
	device_t	child;
	u_int16_t	ports[] = {
				0x300, 0x310, 0x320, 0x330,
				0x340, 0x350, 0x360, 0x370,
				0x200, 0x210, 0x220, 0x230,
				0x240, 0x250, 0x260, 0x270,
				0
			};
	u_int16_t	irqs[] = { 0, 0x09, 0x03, 0x04, 0x05, 0x0a, 0x0b, 0 };
	u_int32_t	port, maddr, msize;
	u_int8_t	irq;
	u_int16_t	data;
	int		i, error;

	for (i = 0; ports[i]; i++) {
		port = ports[i];

		if (ie_ee16_port_check(port)) {
#ifdef DEBUG
			if (bootverbose) {
				device_printf(parent,
					"if_ie: (EE16) not found at port %#x\n",
					port);
			}
#endif
			continue;
		}

		/* reset any ee16 at the current iobase */
		outb(port + IEE16_ECTRL, IEE16_RESET_ASIC);
		outb(port + IEE16_ECTRL, 0);
		DELAY(240);

		data = ie_ee16_hw_read_eeprom(port, IE_EE16_EEPROM_CONFIG1);
		irq = irqs[((data & IE_EE16_EEPROM_IRQ_MASK)
			   >> IE_EE16_EEPROM_IRQ_SHIFT)];

		data = ie_ee16_hw_read_eeprom(port, IE_EE16_EEPROM_MEMCFG);
		maddr = 0xc0000 + ((ffs(data & 0x00ff) - 1) * 0x4000);
		msize = (fls((data & 0x00ff) >> (ffs(data & 0x00ff) - 1)))
			* 0x4000;

		child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "ie", -1);
		device_set_desc_copy(child, desc);
		device_set_driver(child, driver);

		error = bus_set_resource(child, SYS_RES_IRQ, 0, irq, 1);
		if (error) {
			device_printf(parent, "(if_ie) Unable to set IRQ resource %d.\n",
					irq);
			error = device_delete_child(parent, child);
			continue;
		}

		error = bus_set_resource(child, SYS_RES_IOPORT, 0, port, IE_EE16_IOSIZE);
		if (error) {
			device_printf(parent, "(if_ie) Unable to set IOPORT resource %#x-%#x.\n",
					port, port+IE_EE16_IOSIZE);
			error = device_delete_child(parent, child);
			continue;
		}

		error = bus_set_resource(child, SYS_RES_MEMORY, 0, maddr, msize);
		if (error) {
			device_printf(parent, "(if_ie) Unable to set MEMORY resource %#x-%#x.\n",
					maddr, maddr+msize);
			error = device_delete_child(parent, child);
			continue;
		}

		if (bootverbose) {
			device_printf(parent,
				"if_ie: <%s> at port %#x-%#x irq %d iomem %#lx-%#lx (%dKB)\n",
				desc,
				port, (port + IE_EE16_IOSIZE) - 1,
				irq,
				(u_long)maddr, (u_long)(maddr + msize) - 1,
				(msize / 1024));
		}
	}

	return;
}

static int
ie_isa_ee16_probe (device_t dev)
{
	u_int32_t	iobase;

	/* No ISA-PnP support */
	if (isa_get_vendorid(dev))
		return (ENXIO);

	/* No ISA-HINT support */
	if (!device_get_desc(dev))
		return (EBUSY);

	/* Have we at least an ioport? */
	if ((iobase = bus_get_resource_start(dev, SYS_RES_IOPORT, 0)) == 0)
		return (ENXIO);

	/* Is this really an EE16? */
	if (ie_ee16_port_check(iobase))
		return (ENXIO);

	return (0);
}

static int
ie_isa_ee16_attach (device_t dev)
{
	struct ie_softc *	sc;
	int			i, error;
	u_int16_t		checksum;
	u_short			eaddrtemp, pg, adjust, decode, edecode;
	u_char			bart_config;
        
	sc = device_get_softc(dev);

	sc->io_rid = 0;
	sc->irq_rid = 0;
	sc->mem_rid = 0;

	error = ie_alloc_resources(dev);
	if (error) {
		goto bad;
	}

	sc->bus_use = 0;
	sc->ie_reset_586 = ee16_reset_586;
	sc->ie_chan_attn = ee16_chan_attn;
	sc->hard_type = IE_EE16;
	sc->hard_vers = 0;
	sc->iomem = 0;

	/* reset any ee16 at the current iobase */
	outb(PORT(sc) + IEE16_ECTRL, IEE16_RESET_ASIC);
	outb(PORT(sc) + IEE16_ECTRL, 0);
	DELAY(240);

	/* Is this really an EE16? */
	if (ie_ee16_port_check(PORT(sc))) {
		device_printf(dev, "ie_ee16_port_check() failed\n");
		error = ENXIO;
		goto bad;
	}

	/* need to put the 586 in RESET while we access the eeprom. */
	outb(PORT(sc) + IEE16_ECTRL, IEE16_RESET_586);

	/* read the eeprom and checksum it, should == IE_E16_ID */
	checksum = 0;
	for (i = 0; i < 0x40; i++)
		checksum += ie_ee16_hw_read_eeprom(PORT(sc), i);

	if (checksum != IE_EE16_ID) {
		device_printf(dev, "invalid eeprom checksum: %x\n", checksum);
		error = ENXIO;
		goto bad;
	}

	if ((kvtop(sc->iomembot) < 0xC0000) ||
	     (kvtop(sc->iomembot) + sc->iosize > 0xF0000)) {
		device_printf(sc->dev, "mapped memory location %p out of range\n",
			(void *)sc->iomembot);
		error = ENXIO;
		goto bad;
	}

	pg = ((kvtop(sc->iomembot)) & 0x3C000) >> 14;
	adjust = IEE16_MCTRL_FMCS16 | (pg & 0x3) << 2;
	decode = ((1 << (sc->iosize / 16384)) - 1) << pg;
	edecode = ((~decode >> 4) & 0xF0) | (decode >> 8);

	/* ZZZ This should be checked against eeprom location 6, low byte */
	outb(PORT(sc) + IEE16_MEMDEC, decode & 0xFF);
	/* ZZZ This should be checked against eeprom location 1, low byte */
	outb(PORT(sc) + IEE16_MCTRL, adjust);
	/* ZZZ Now if I could find this one I would have it made */
	outb(PORT(sc) + IEE16_MPCTRL, (~decode & 0xFF));
	/* ZZZ I think this is location 6, high byte */
	outb(PORT(sc) + IEE16_MECTRL, edecode); /* XXX disable Exxx */

#if 0
	(void) kvtop(sc->iomembot);
#endif

	/*
	 * first prime the stupid bart DRAM controller so that it works,
	 * then zero out all of memory.
	 */
	bzero(sc->iomembot, 32);
	bzero(sc->iomembot, sc->iosize);

	/* Get the encoded interrupt number from the EEPROM */
	sc->irq_encoded = ie_ee16_hw_read_eeprom(PORT(sc),
						 IE_EE16_EEPROM_CONFIG1);
	sc->irq_encoded = (sc->irq_encoded & IE_EE16_EEPROM_IRQ_MASK) >>
			   IE_EE16_EEPROM_IRQ_SHIFT;

	/*
	 * Get the hardware ethernet address from the EEPROM and save it in
	 * the softc for use by the 586 setup code.
	 */
	eaddrtemp = ie_ee16_hw_read_eeprom(PORT(sc), IEE16_EEPROM_ENET_HIGH);
	sc->enaddr[1] = eaddrtemp & 0xFF;
	sc->enaddr[0] = eaddrtemp >> 8;
	eaddrtemp = ie_ee16_hw_read_eeprom(PORT(sc), IEE16_EEPROM_ENET_MID);
	sc->enaddr[3] = eaddrtemp & 0xFF;
	sc->enaddr[2] = eaddrtemp >> 8;
	eaddrtemp = ie_ee16_hw_read_eeprom(PORT(sc), IEE16_EEPROM_ENET_LOW);
	sc->enaddr[5] = eaddrtemp & 0xFF;
	sc->enaddr[4] = eaddrtemp >> 8;

	/* disable the board interrupts */
	outb(PORT(sc) + IEE16_IRQ, sc->irq_encoded);

	/* enable loopback to keep bad packets off the wire */
	bart_config = inb(PORT(sc) + IEE16_CONFIG);
	bart_config |= IEE16_BART_LOOPBACK;
	bart_config |= IEE16_BART_MCS16_TEST;/* inb doesn't get bit! */
	outb(PORT(sc) + IEE16_CONFIG, bart_config);
	bart_config = inb(PORT(sc) + IEE16_CONFIG);

	/* take the board out of reset state */
	outb(PORT(sc) + IEE16_ECTRL, 0);
	DELAY(100);

	if (!check_ie_present(sc)) {
		device_printf(dev, "check_ie_present() returned false.\n");
		error = ENXIO;
		goto bad;
	}

	error = ie_attach(dev);
	if (error) {
		device_printf(dev, "ie_attach() failed.\n");
		goto bad;
	}

	error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET,
				ie_intr, sc, &sc->irq_ih);
	if (error) {
		device_printf(dev, "Unable to register interrupt handler\n"); 
		goto bad;
	}

	return (0);
bad:
	ie_release_resources(dev);

	return (error);
}

/*
 * If an EE16 is present, return 0
 * else, return 1.
 */
static int
ie_ee16_port_check (u_int32_t port)
{
	int		i;
	u_int16_t	board_id;
	u_int8_t	data;

	board_id = 0;
	for (i = 0; i < 4; i++) {
		data = inb(port + IE_EE16_ID_PORT);
		board_id |= ((data >> 4) << ((data & 0x03) << 2));
	}

	if (board_id != IE_EE16_ID)
		return (1);

	return (0);
}

static void
ie_ee16_hw_eeprom_clock (u_int32_t port, int state)
{
	u_int8_t	ectrl;

	ectrl = inb(port + IEE16_ECTRL);
	ectrl &= ~(IEE16_RESET_ASIC | IEE16_ECTRL_EESK);

	if (state) {
		ectrl |= IEE16_ECTRL_EESK;
	}
	outb(port + IEE16_ECTRL, ectrl);
	DELAY(9);		/* EESK must be stable for 8.38 uSec */
}

static void
ie_ee16_hw_eeprom_out (u_int32_t port, u_int16_t edata, int count)
{
	u_int8_t	ectrl;
	int		i;

	ectrl = inb(port + IEE16_ECTRL);
	ectrl &= ~IEE16_RESET_ASIC;

	for (i = count - 1; i >= 0; i--) {
		ectrl &= ~IEE16_ECTRL_EEDI;
		if (edata & (1 << i)) {
			ectrl |= IEE16_ECTRL_EEDI;
		}
		outb(port + IEE16_ECTRL, ectrl);
		DELAY(1);       /* eeprom data must be setup for 0.4 uSec */
		ie_ee16_hw_eeprom_clock(port, 1);
		ie_ee16_hw_eeprom_clock(port, 0);
	}
	ectrl &= ~IEE16_ECTRL_EEDI;
	outb(port + IEE16_ECTRL, ectrl);
	DELAY(1);               /* eeprom data must be held for 0.4 uSec */

	return;
}

static u_int16_t
ie_ee16_hw_eeprom_in (u_int32_t port)
{
	u_int8_t	ectrl;
	u_int16_t	edata;
	int		i;

	ectrl = inb(port + IEE16_ECTRL);
	ectrl &= ~IEE16_RESET_ASIC;

	for (edata = 0, i = 0; i < 16; i++) {
		edata = edata << 1;
		ie_ee16_hw_eeprom_clock(port, 1);
		ectrl = inb(port + IEE16_ECTRL);
		if (ectrl & IEE16_ECTRL_EEDO) {
			edata |= 1;
		}
		ie_ee16_hw_eeprom_clock(port, 0);
	}
	return (edata);
}

static u_int16_t
ie_ee16_hw_read_eeprom (u_int32_t port, int loc)
{
	u_int8_t	ectrl;
	u_int16_t	edata;

	ectrl = inb(port + IEE16_ECTRL);
	ectrl &= IEE16_ECTRL_MASK;
	ectrl |= IEE16_ECTRL_EECS;
	outb(port + IEE16_ECTRL, ectrl);

	ie_ee16_hw_eeprom_out(port, IEE16_EEPROM_READ, IEE16_EEPROM_OPSIZE1);
	ie_ee16_hw_eeprom_out(port, loc, IEE16_EEPROM_ADDR_SIZE);
	edata = ie_ee16_hw_eeprom_in(port);

	ectrl = inb(port + IEE16_ECTRL);
	ectrl &= ~(IEE16_RESET_ASIC | IEE16_ECTRL_EEDI | IEE16_ECTRL_EECS);
	outb(port + IEE16_ECTRL, ectrl);

	ie_ee16_hw_eeprom_clock(port, 1);
	ie_ee16_hw_eeprom_clock(port, 0);

	return (edata);
}

/*
 * AT&T StarLan/
 */

static int
ie_isa_sl_probe (device_t dev)
{
	u_int32_t	iobase;

	/* No ISA-PnP support */
	if (isa_get_vendorid(dev))
		return (ENXIO);

	/* ISA-HINT support only! */
	if (device_get_desc(dev))
		return (EBUSY);

	/* Have we at least an ioport? */
	if ((iobase = bus_get_resource_start(dev, SYS_RES_IOPORT, 0)) == 0)
		return (ENXIO);

	/* Is this really an SL board? */
	if (ie_isa_sl_get_hard_type(iobase) == IE_NONE)
		return (ENXIO);

	return (ENXIO);
}

static int
ie_isa_sl_attach (device_t dev)
{
	struct ie_softc *	sc;
	int			error;

	sc = device_get_softc(dev);

	sc->io_rid = 0;
	sc->irq_rid = 0;
	sc->mem_rid = 0;

	error = ie_alloc_resources(dev);
	if (error) {
		goto bad;
	}

	/* Is this really an SL board? */
	if ((sc->hard_type = ie_isa_sl_get_hard_type(PORT(sc))) == IE_NONE) {
		error = ENXIO;
		goto bad;
	}

	sc->hard_vers = SL_REV(inb(PORT(sc) + IEATT_REVISION));
	if (sc->hard_type == IE_NI5210) {
		sc->bus_use = 1;
	} else {
		sc->bus_use = 0;
	}

	sc->ie_reset_586 = sl_reset_586;
	sc->ie_chan_attn = sl_chan_attn;

	if (!check_ie_present(sc)) {
		error = ENXIO;
		goto bad;
	}

	switch (sc->hard_type) {
		case IE_EN100:
		case IE_STARLAN10:
		case IE_SLFIBER:
		case IE_NI5210:
			sl_read_ether(sc, sc->enaddr);
			break;
		default:
			if (bootverbose)
				device_printf(sc->dev, "unknown AT&T board type code %d\n", sc->hard_type);
			error = ENXIO;
			goto bad;
			break;
	}

	error = ie_attach(dev);
	if (error) {
		device_printf(dev, "ie_attach() failed.\n");
		goto bad;
	}

	error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET,
				ie_intr, sc, &sc->irq_ih);
	if (error) {
		device_printf(dev, "Unable to register interrupt handler\n"); 
		goto bad;
	}

	return (0);
bad:
	ie_release_resources(dev);

	return (error);
}

static enum ie_hardware
ie_isa_sl_get_hard_type (u_int32_t port)
{
	u_char			c;
	enum ie_hardware	retval;

	c = inb(port + IEATT_REVISION);
	switch (SL_BOARD(c)) {
		case SL1_BOARD:
			if (inb(port + IEATT_ATTRIB) != NI5210_BOARD)
				retval = IE_NONE;
			retval = IE_NI5210;
			break;
		case SL10_BOARD:
			retval = IE_STARLAN10;
			break;
		case EN100_BOARD:
			retval = IE_EN100;
			break;
		case SLFIBER_BOARD:
			retval = IE_SLFIBER;
			break;
		default:
			retval = IE_NONE;
	}
	return (retval);
}

static devclass_t ie_devclass;

static device_method_t ie_isa_3C507_methods[] = {
	DEVMETHOD(device_identify,	ie_isa_3C507_identify),
	DEVMETHOD(device_probe,		ie_isa_3C507_probe),
	DEVMETHOD(device_attach,	ie_isa_3C507_attach),
	DEVMETHOD(device_detach,	ie_detach),
	{ 0, 0 }
};
static driver_t ie_isa_3C507_driver = {
	"ie",
	ie_isa_3C507_methods,
	sizeof(struct ie_softc), 
};
DRIVER_MODULE(ie_3C507, isa, ie_isa_3C507_driver, ie_devclass, ie_modevent, 0);
MODULE_DEPEND(ie_3C507, elink, 1, 1, 1);

static device_method_t ie_isa_ee16_methods[] = {
	DEVMETHOD(device_identify,	ie_isa_ee16_identify),
	DEVMETHOD(device_probe,		ie_isa_ee16_probe),
	DEVMETHOD(device_attach,	ie_isa_ee16_attach),
	DEVMETHOD(device_detach,	ie_detach),
	{ 0, 0 }
};
static driver_t ie_isa_ee16_driver = {
	"ie",
	ie_isa_ee16_methods,
	sizeof(struct ie_softc), 
};
DRIVER_MODULE(ie_EE16, isa, ie_isa_ee16_driver, ie_devclass, ie_modevent, 0);

static device_method_t ie_isa_sl_methods[] = {
	DEVMETHOD(device_probe,		ie_isa_sl_probe),
	DEVMETHOD(device_attach,	ie_isa_sl_attach),
	DEVMETHOD(device_detach,	ie_detach),
	{ 0, 0 }
};
static driver_t ie_isa_sl_driver = {
	"ie",
	ie_isa_sl_methods,
	sizeof(struct ie_softc), 
};
DRIVER_MODULE(ie_SL, isa, ie_isa_sl_driver, ie_devclass, ie_modevent, 0);

static int
ie_modevent (mod, what, arg)
	module_t	mod;
	int		what;
	void *		arg;
{
	device_t *	devs;
	int		count;
	int		i;

	switch (what) {
	case MOD_LOAD:
		break;
	case MOD_UNLOAD:
		devclass_get_devices(ie_devclass, &devs, &count);
		for (i = 0; i < count; i++)
			device_delete_child(device_get_parent(devs[i]), devs[i]);
		break;
	default:
		break;
	};

	return (0);
}
