#include "string.h"
#include "io.h"
#include "timer.h"
#include "console.h"
#include "dev.h"
#include "eisa.h"

/*
 * Increment a bus_loc structure to the next possible EISA location.
 * Leave the structure zeroed and return 0 if there are no more valid
 * locations.
 *
 */
static int eisa_next_location ( struct bus_loc *bus_loc ) {
	struct eisa_loc *eisa_loc = ( struct eisa_loc * ) bus_loc;
	
	/*
	 * Ensure that there is sufficient space in the shared bus
	 * structures for a struct isa_loc and a struct
	 * isa_dev, as mandated by bus.h.
	 *
	 */
	BUS_LOC_CHECK ( struct eisa_loc );
	BUS_DEV_CHECK ( struct eisa_device );

	return ( eisa_loc->slot = ( ++eisa_loc->slot & EISA_MAX_SLOT ) );
}

/*
 * Fill in parameters for an EISA device based on slot number
 *
 * Return 1 if device present, 0 otherwise
 *
 */
static int eisa_fill_device  ( struct bus_dev *bus_dev,
			       struct bus_loc *bus_loc ) {
	struct eisa_loc *eisa_loc = ( struct eisa_loc * ) bus_loc;
	struct eisa_device *eisa = ( struct eisa_device * ) bus_dev;
	uint8_t present;

	/* Copy slot number to struct eisa, set default values */
	eisa->slot = eisa_loc->slot;
	eisa->name = "?";

	/* Slot 0 is never valid */
	if ( ! eisa->slot )
		return 0;

	/* Set ioaddr */
	eisa->ioaddr = EISA_SLOT_BASE ( eisa->slot );

	/* Test for board present */
	outb ( 0xff, eisa->ioaddr + EISA_MFG_ID_HI );
	present = inb ( eisa->ioaddr + EISA_MFG_ID_HI );
	if ( present & 0x80 ) {
		/* No board present */
		return 0;
	}

	/* Read mfg and product IDs.  Yes, the resulting uint16_ts
	 * will be upside-down.  This appears to be by design.
	 */
	eisa->mfg_id = ( inb ( eisa->ioaddr + EISA_MFG_ID_LO ) << 8 )
		+ present;
	eisa->prod_id = ( inb ( eisa->ioaddr + EISA_PROD_ID_LO ) << 8 )
		+ inb ( eisa->ioaddr + EISA_PROD_ID_HI );

	DBG ( "EISA found slot %hhx (base %#hx) ID %hx:%hx (\"%s\")\n",
	      eisa->slot, eisa->ioaddr, eisa->mfg_id, eisa->prod_id,
	      isa_id_string ( eisa->mfg_id, eisa->prod_id ) );

	return 1;
}

/*
 * Test whether or not a driver is capable of driving the device.
 *
 */
static int eisa_check_driver ( struct bus_dev *bus_dev,
				 struct device_driver *device_driver ) {
	struct eisa_device *eisa = ( struct eisa_device * ) bus_dev;
	struct eisa_driver *driver
		= ( struct eisa_driver * ) device_driver->bus_driver_info;
	unsigned int i;

	/* Compare against driver's ID list */
	for ( i = 0 ; i < driver->id_count ; i++ ) {
		struct eisa_id *id = &driver->ids[i];
		
		if ( ( eisa->mfg_id == id->mfg_id ) &&
		     ( ISA_PROD_ID ( eisa->prod_id ) ==
		       ISA_PROD_ID ( id->prod_id ) ) ) {
			DBG ( "EISA found ID %hx:%hx (\"%s\") "
			      "(device %s) matching driver %s\n",
			      eisa->mfg_id, eisa->prod_id,
			      isa_id_string ( eisa->mfg_id,
					      eisa->prod_id ),
			      id->name, driver->name );
			eisa->name = id->name;
			return 1;
		}
	}

	/* No device found */
	return 0;
}

/*
 * Describe an EISA device
 *
 */
static char * eisa_describe_device ( struct bus_dev *bus_dev ) {
	struct eisa_device *eisa = ( struct eisa_device * ) bus_dev;
	static char eisa_description[] = "EISA 00";

	sprintf ( eisa_description + 5, "%hhx", eisa->slot );
	return eisa_description;
}

/*
 * Name an EISA device
 *
 */
static const char * eisa_name_device ( struct bus_dev *bus_dev ) {
	struct eisa_device *eisa = ( struct eisa_device * ) bus_dev;
	
	return eisa->name;
}

/*
 * EISA bus operations table
 *
 */
struct bus_driver eisa_driver __bus_driver = {
	.name			= "EISA",
	.next_location		= eisa_next_location,
	.fill_device		= eisa_fill_device,
	.check_driver		= eisa_check_driver,
	.describe_device	= eisa_describe_device,
	.name_device		= eisa_name_device,
};

/*
 * Fill in a nic structure
 *
 */
void eisa_fill_nic ( struct nic *nic, struct eisa_device *eisa ) {

	/* Fill in ioaddr and irqno */
	nic->ioaddr = eisa->ioaddr;
	nic->irqno = 0;

	/* Fill in DHCP device ID structure */
	nic->dhcp_dev_id.bus_type = ISA_BUS_TYPE;
	nic->dhcp_dev_id.vendor_id = htons ( eisa->mfg_id );
	nic->dhcp_dev_id.device_id = htons ( eisa->prod_id );
}

/*
 * Reset and enable/disable an EISA device
 *
 */
void eisa_device_enabled ( struct eisa_device *eisa, int enabled ) {
	/* Set reset line high for 1000 µs.  Spec says 500 µs, but
	 * this doesn't work for all cards, so we are conservative.
	 */
	outb ( EISA_CMD_RESET, eisa->ioaddr + EISA_GLOBAL_CONFIG );
	udelay ( 1000 ); /* Must wait 800 */

	/* Set reset low and write a 1 to ENABLE.  Delay again, in
	 * case the card takes a while to wake up.
	 */
	outb ( enabled ? EISA_CMD_ENABLE : 0,
	       eisa->ioaddr + EISA_GLOBAL_CONFIG );
	udelay ( 1000 ); /* Must wait 800 */

	DBG ( "EISA %s device %hhx\n", ( enabled ? "enabled" : "disabled" ),
	      eisa->slot );
}
