/* eexpress.c: Diagnostic program for Intel EtherExpress 16.

	This program must be compiled with '-O' (Optimize, not zero).
	If you have unresolved references to e.g. inw(), then you haven't used -O.
	The suggested compile command is at the bottom of this file.

	Written 1993,1994,2000 by Donald Becker.
	Copyright 1994,2000 Donald Becker
	Copyright 1993 United States Government as represented by the
	Director, National Security Agency.

	This software may be used and distributed according to the terms of
	the GNU General Public License (GPL), incorporated herein by reference.
	This program is licensed exclusively under the GPL.

	The author may be reached as becker@scyld.com, or C/O
	 Scyld Computing Corporation
	 914 Bay Ridge Road, Suite 220
	 Annapolis MD 21403

	Support and updates are available at
	http://www.scyld.com/diag/index.html

	Simplified license statement: Using any portion of this program in
	your own program means that you must give credit to the original author
	and release the resulting source code under the GPL.  You must include the
	text of the license with any redistribution.  See
	 http://www.gnu.org/copyleft/gpl.txt
*/

static const char cvs_id[] =
"$Id: eexpress.c,v 1.3 2005/03/22 04:44:21 becker Exp $";

static const char version_msg[] =
"eexpress.c:v1.03 3/21/2005 Donald Becker http://scyld.com/diag/index.html\n";

static const char usage_msg[] =
"Usage: eexpress [-afhNsvVwW] [-p <IOport>] [-F <xcvr-type>] [-P <newIOport>]\n\
   -p <I/O base address>	Use the card at this I/O address (default 0x300).\n\
  EEPROM configuration commands take effect at the next reset\n\
   -F 10baseT, 10base2, AUI, 10baset   Set the specified transceiver type.\n\
   -Q 3, 4, 5, 9, 10, 11, 12, 15       Set the IRQ line.\n\
   -P 0x240, 0x280, 0x2C0, 0x300, 0x320, 0x340, 0x360  New I/O base.\n\
";

#if ! defined(__OPTIMIZE__)
#warning  You must compile this program with the correct options!
#error You must compile this driver with "-O".
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#if defined(__linux__)  &&  __GNU_LIBRARY__ == 1
#include <asm/io.h>			/* Newer libraries use <sys/io.h> instead. */
#else
#include <sys/io.h>
#endif


/* Offsets from the base address. */
#define DATA_REG		0		/* Data Transfer Register. */
#define WRITE_PTR		2		/* Write Address Pointer. */
#define Read_Ptr		4		/* Read Address Pointer. */
#define CA_Ctrl			6		/* Channel Attention Control. */
#define Sel_IRQ			7		/* IRQ Select. */
#define SMB_Ptr			8		/* Shadow Memory Bank Pointer. */
#define MEM_Ctrl		11
#define MEM_Page_Ctrl	12
#define Config			13
#define EEPROM_Ctrl		14
#define ID_PORT			15

/*	EEPROM_Ctrl bits. */

#define EE_SHIFT_CLK	0x01	/* EEPROM shift clock. */
#define EE_CS			0x02	/* EEPROM chip select. */
#define EE_DATA_WRITE	0x04	/* EEPROM chip data in. */
#define EE_DATA_READ	0x08	/* EEPROM chip data out. */
#define EE_CTRL_BITS	(EE_SHIFT_CLK | EE_CS | EE_DATA_WRITE | EE_DATA_READ)
#define ASIC_RESET		0x40
#define _586_RESET		0x80

/* No delay is needed between EEPROM clock transitions. */
#define eeprom_delay()	do { } while(0)
#define EE_WRITE_CMD	(5 << 6)
#define EE_READ_CMD		(6 << 6)
#define EE_ERASE_CMD	(7 << 6)

struct option longopts[] = {
 /* { name	has_arg	 *flag	val } */
	{"base-address", 1, 0, 'p'},
	{"all",		   0, 0, 'a'},	/* Print all registers. */
	{"help",	   0, 0, 'h'},	/* Give help */
	{"interface",  0, 0, 'f'},	/* Interface number (built-in, AUI) */
	{"irq",		   1, 0, 'i'},	/* Interrupt number */
	{"verbose",	   0, 0, 'v'},	/* Verbose mode */
	{"version",	   0, 0, 'V'},	/* Display version number */
	{"write-EEPROM", 1, 0, 'w'},/* Write th EEPROMS with the specified vals */
	{ 0, 0, 0, 0 }
};

#define printk printf

int verbose = 0;
struct device {
	char *name;
	short base_addr;
	int tbusy;
	int trans_start;
	unsigned char dev_addr[6];	/* Hardware station address. */
} devs, *dev = &devs;

unsigned char fake_packet[100];

int expprobe(short ioaddr);
void show_eeprom(short ioaddr);
int read_eeprom(int ioaddr, int location);
int check_eeprom(short ioaddr);
int reset_board(short ioaddr);


int
main(int argc, char **argv)
{
	int port_base = 0x300, irq = -1;
	int errflag = 0, show_version = 0;
	int write_eeprom = 0, interface = -1, all_regs = 0;
	int c, longind;
	extern char *optarg;

	while ((c = getopt_long(argc, argv, "af:hi:p:Q:vVw", longopts, &longind))
		   != -1)
		switch (c) {
		case 'a': all_regs++;				break;
		case 'f': interface = atoi(optarg);	break;
		case 'h':  printf(usage_msg); return 0;
		case 'Q':
		case 'i': irq = atoi(optarg);		break;
		case 'p': port_base = strtol(optarg, NULL, 16); break;
		case 'v': verbose++;			break;
		case 'V': show_version++;		break;
		case 'w': write_eeprom++;		break;
		case '?':
			errflag++;
		}
	if (errflag) {
		fprintf(stderr, usage_msg);
		return 2;
	}

	if (verbose || show_version)
		printf(version_msg);

	dev->name = "eexpress";
	
	if (ioperm(0x80, 2, 1) < 0
		|| ioperm(port_base, 18, 1) < 0) {
		fprintf(stderr, "eexpress-diag: ioperm() failure: %s\n",
				strerror(errno));
		fprintf(stderr,
				" This program directly accesses hardware device registers \n\
 and thus must be run as 'root'.  This program may interfere with current\n\
 network traffic when run, and may change the hardware configuration.\n");
		return 2;
	}

	reset_board(port_base);

	expprobe(port_base);

	if (verbose) {
		show_eeprom(port_base);
	}
	check_eeprom(port_base);

	return 0;
}

int expmagic(short ioaddr)
{
	short id_addr = ioaddr + ID_PORT;
	unsigned short sum = 0;
	int i;
	for (i = 4; i > 0; i--) {
		short id_val = inb(id_addr);
		sum |= (id_val >> 4) << ((id_val & 3) << 2);
	}
	return sum;
}

int expprobe(short ioaddr)
{
	unsigned short station_addr[3];
	unsigned short sum = expmagic(ioaddr);
	short i;

	if (sum != 0xbaba) {
		printf("Probe failed ID checksum, expected 0xbaba, got %#04x.\n", sum);
		return 1;
	}

	printf("EtherExpress found at %#x, station address", ioaddr);

	/* The station address is stored backwards in the EEPROM, reverse after reading. */
	station_addr[0] = read_eeprom(ioaddr, 2);
	station_addr[1] = read_eeprom(ioaddr, 3);
	station_addr[2] = read_eeprom(ioaddr, 4);
	for (i = 0; i < 6; i++) {
		dev->dev_addr[i] = ((unsigned char*)station_addr)[5-i];
		printf(" %02x", dev->dev_addr[i]);
	}

	{
		char irqmap[] = {0, 9, 3, 4, 5, 10, 11, 0};
		short irqval = read_eeprom(ioaddr, 0) >> 13;
		printf(", IRQ %d\n", irqmap[irqval & 7]);
	}
	return 0;
}

void
show_eeprom(short ioaddr)
{
	int j;

	for (j = 0; j < 16; j++) {
		printk(" EEPROM location %2x is %4.4x\n", j, read_eeprom(ioaddr, j));
	}
}

int
read_eeprom(int ioaddr, int location)
{
	int i;
	unsigned short retval = 0;
	short ee_addr = ioaddr + EEPROM_Ctrl;
	int read_cmd = location | EE_READ_CMD;
	short ctrl_val = inb(ee_addr) & ~ASIC_RESET & ~EE_CTRL_BITS;

	if (verbose > 2) printk(" EEPROM reg @ %x (%2x) ", ee_addr, ctrl_val);

	ctrl_val |= EE_CS;
	outb(ctrl_val, ee_addr);

	/* Shift the read command bits out. */
	for (i = 8; i >= 0; i--) {
		short outval = (read_cmd & (1 << i)) ? ctrl_val | EE_DATA_WRITE : ctrl_val;
		if (verbose > 3) printf("%x ", outval);
		outb(outval, ee_addr);
		outb(outval | EE_SHIFT_CLK, ee_addr);	/* Give the EEPROM a clock tick. */
		eeprom_delay();
		outb(outval, ee_addr);	/* Give the EEPROM a clock tick. */
		eeprom_delay();
	}
	if (verbose > 3) printf(" ");
	outb(ctrl_val, ee_addr);

	for (i = 16; i > 0; i--) {
		outb(ctrl_val | EE_SHIFT_CLK, ee_addr);	 eeprom_delay();
		retval = (retval << 1) | ((inb(ee_addr) & EE_DATA_READ) ? 1 : 0);
		outb(ctrl_val, ee_addr);  eeprom_delay();
	}

	/* Terminate the EEPROM access. */
	ctrl_val &= ~EE_CS;
	outb(ctrl_val | EE_SHIFT_CLK, ee_addr);
	eeprom_delay();
	outb(ctrl_val, ee_addr);
	eeprom_delay();

	return retval;
}

int
check_eeprom(short ioaddr)
{
	int i;
	unsigned short sum = 0;

	for (i = 0; i < 64; i++)
		sum += read_eeprom(ioaddr, i);
	printk("EEPROM checksum is %#04x, which is %s.\n", sum,
		   sum == 0xbaba ? "correct" : "bad (correct value is 0xbaba)" );
	return sum != 0xbaba;
}

int
reset_board(short ioaddr)
{
	int boguscnt = 1000;

	outb(ASIC_RESET, ioaddr + EEPROM_Ctrl);
	outb(0x00, ioaddr + EEPROM_Ctrl);
	while (--boguscnt > 0)
		if (expmagic(ioaddr) == 0xbaba) {
			if (verbose > 1)
				printk("Exited reset after %d checks.\n", 1000 - boguscnt);
			return 0;
		}

	printk("Failed to find the board after a reset.\n");
	return 1;
}


/*
 * Local variables:
 *  compile-command: "cc -O -Wall -o eexpress eexpress.c"
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 * End:
 */
