/* atlantic.c: Setup program for AT/LANTIC DP83905 ethercards. */
/*
	This is a setup and diagnostic program for ISA NE2000 Ethernet adapters.
	It has specific support for configuring the National Semiconductor
	AT/LANTIC DP83905 used in on ISA Ethernet adapters such as the NE2000plus.
	It also works several work-alike chips from other vendors.

	This program must be compiled with '-O'.
	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.

	Copyright 1994-2000 by Donald Becker.
	This software may be used and distributed according to the terms of
	the GNU General Public License (GPL), incorporated herein by reference.
	Contact the author for use under other terms.

	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

	Common-sense licensing 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 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: atlantic.c,v 1.8 2005/03/22 00:44:43 becker Exp $";

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

static const char usage_msg[] =
"Usage: atlantic [-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\
   -N or -W         Set to NE2000 or WD8013 clone mode (mutually exclusive)\n\
";

#if ! defined(__OPTIMIZE__)
#warning  You must compile this program with the correct options!
#error You must compile this driver with "-O".
#endif
#include <unistd.h>				/* Hey, we're all POSIX here, right? */
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>

#if defined(__linux__)  &&  __GNU_LIBRARY__ == 1
#include <asm/io.h>			/* Newer libraries use <sys/io.h> instead. */
#else
#include <sys/io.h>
#endif

/* Two functions that some GLIBC header file sets omit.  grrr... */
extern int ioperm(unsigned long __base, unsigned long extent, int on_or_off);
extern int iopl(int level);

struct option longopts[] = {
 /* { name	has_arg	 *flag	val } */
	{"base-address", 1, 0, 'p'}, /* Base I/O port address. */
	{"help",	   0, 0, 'h'},	/* Give help */
	{"interface",  0, 0, 'F'},	/* Set the transceiver type (built-in, AUI) */
	{"NE2000",	   1, 0, 'N'},	/* Set to NE2000 mode */
	{"new_ioport", 1, 0, 'P'},	/* Set a I/O location for the card. */
	{"irq",		   1, 0, 'Q'},	/* Set a new IRQ (interrupt) line. */
	{"wd8013",	   1, 0, 'W'},	/* Set to WD8013 mode */
	{"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 }
};

/* Three configuration registers are read and may be set from the EEPROM.
   They are named registers A, B and C

   RegA		NE/WD   FREAD  INT2   INT1   INT0  IOAD2  IOAD1  IOAD0
   RegB		EELOAD  BTPR   BUSE   CHRDY  IO16  GDLINK XCVR1  XCVR0
   RegC		SoftEn  ClkSel IntMde COMP   BtPR3 BtPR2  BtPR1  BtPROM0
*/
/* I/O base settings, IOAD2-0, in register A. */
static int io_base[8] = {
	0x300, 0x278, 0x240, 0x280, 0x2C0, 0x320, 0x340, 0x360};
/* The IRQ line settings, INT2-0, in register A, and a reverse map. */
static int index2irq[8] = {3, 4, 5, 9, 10, 11, 12, 15};
static int irq2index[16] = {-1, -1, 3, 0, 1, 2, -1, -1,
							-1, 3, 4, 5, 6, -1, -1, 7};
/* The transceiver settings, XCVR1-0, in register B.
   10baseT-LRT is Low Receive Threshold -- "turning the volume up" for long
   wiring runs.
*/
static char *xcvr_name[4] = {"10baseT", "Thinnet", "AUI", "10baseT (LRT)"};

int opt_f = 0;

static void show_config(int regA, int regB);
static void write_EEPROM(int dp8390_base, int regA, int regB, int regC);


/*
 I should say something here... uuhhhhmmm....
 */

int
main(int argc, char *argv[])
{
	extern char *optarg;
	int port_base = 0x300;		/* Base address of the board. */
	int	ioaddr = 0x300;			/* Base address of the 8390 registers. */
	int new_ioport = -1, new_irq = -1, new_mode = 0;
	int errflag = 0, verbose = 0, wd_mode = 0, write_eeprom = 0;
	int show_version = 0, opt_a = 0;
	int new_io_idx = -1, xcvr = -1;	/* I/O port, Transceiver type to set. */
	int regA, regB, old_regA, old_regB;
	int option;
	char *endptr;

	while ((option = getopt(argc, argv, "afF:hi:Q:m:Np:P:vVwW")) != -1)
		switch (option) {
		case 'a':  opt_a++; break;
		case 'f':  opt_f++; break;
		case 'F':
			if (strncmp(optarg, "10base", 6) == 0) {
				switch (optarg[6]) {
				case 'T':  xcvr = 0; break;
				case '2':  xcvr = 1; break;
				case '5':  xcvr = 2; break;
				default: errflag++;
				}
			} else if (strcmp(optarg, "AUI") == 0)
				xcvr = 2;
			else if (optarg[0] >= '0' &&  optarg[0] <= '3'
					 &&  optarg[1] == 0)
				xcvr = optarg[0] - '0';
			else {
				fprintf(stderr, "Invalid interface specified: it must be"
						" 0..3, '10base{T,2,5}' or 'AUI'.\n");
				errflag++;
			}
			break;
		case 'h':  printf(usage_msg); return 0;
		case 'N':  wd_mode = 0; new_mode = 2; break;
		case 'i':
		case 'Q':
			new_irq = strtol(optarg, &endptr, 10);
			if (endptr[0] != 0) {
				fprintf(stderr, "Invalid non-numeric new IRQ '%s'."
						"  Valid values: 3-5,7,9-15.\n", optarg);
				errflag++;
				break;
			}
			if (new_irq < 3 || new_irq > 15 || new_irq == 6 || new_irq == 8) {
				fprintf(stderr, "Invalid new IRQ %#x.  Valid values: "
						"3-5,7,9-15.\n", new_irq);
				errflag++;
			}
			break;
		case 'p':  port_base = strtol(optarg, NULL, 16);  break;
		case 'P': {
			char *endptr;
			unsigned long new_ioaddr = strtoul(optarg, &endptr, 16);
			if (*endptr) {
				fprintf(stderr, "Invalid new I/O port '%s'.\n", optarg);
				errflag++;
			} else if ((new_ioaddr & ~0x3f0) && new_ioaddr != 0x278) {
				fprintf(stderr, "Invalid new I/O address %#x.  Valid "
						"settings are :\n   0x300,0x320,0x340,0x360,"
						"0x240,0x280,0x2C0, or 0x278.\n", (int)new_ioaddr);
				errflag++;
			} else
				new_ioport = new_ioaddr;
			break;
		}
		case 'v':  verbose++;	 break;
		case 'V':  show_version++;		 break;
		case 'w':  write_eeprom++;		 break;
		case 'W':  wd_mode++; new_mode = 1; break;
		case '?':
			errflag++;
		}
	if (errflag) {
		fprintf(stderr, usage_msg);
		return 3;
	}

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

	/* Turn on access to the I/O ports. */
	if (ioperm(port_base, 32, 1) < 0) {
		fprintf(stderr, "atlantic: 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;
	}

	/* Check the specified new I/O port value. */
	if (new_irq >= 0  &&  (new_irq >= 16  ||  irq2index[new_irq] < 0)) {
		fprintf(stderr, "The new IRQ line, %d, is invalid.\n",
				new_irq);
		return 3;
	}
	if (new_ioport >= 0) {
		int i;
		for (i = 0; i < 8; i++)
			if (new_ioport == io_base[i])
				 break;
		if (i >= 8) {
			fprintf(stderr, "The new base I/O address, %#x, is invalid.\n",
					new_ioport);
			return 3;
		}
		new_io_idx = i;
	}

	if ( ! opt_f  &&  inb(port_base) == 0xff) {
		printf("No AT/LANTIC chip found at I/O %#x.\n"
			   " Use '-f' to override if you are certain the chip is at this"
			   " I/O location.\n", port_base);
		return 1;
	}
		
	/* First find if this card is set to WD or NE mode by locating the 8390
	   registers. */
	{
		int saved_cmd = inb(port_base);
		int cntr;
		outb(0x20, port_base);
		inb(port_base + 13);	/* Clear a counter register */
		cntr = inb(port_base + 13);
		if (verbose)
			printf("  The NE2K 8390 cmd register was %2.2x, cntr %d.\n",
				   saved_cmd, cntr);
		if (cntr)
			ioaddr = port_base + 16;
		else
			ioaddr = port_base;
		outb(saved_cmd, port_base);
	}

	if (opt_a) {
		int saved_0 = inb(port_base);
		int window, i;

		printf("8390 registers at %#x (command register was %2.2x)\n",
			   ioaddr, saved_0);
		for (window = 0; window < 4; window++) {
			printf(" Window %d:", window);
			outb((window<<6) | 0x20, ioaddr);
			for(i = 0; i < 16; i++) {
				if (window == 0 && i == 6)
					printf(" **");
				else
					printf(" %2.2x", inb(ioaddr + i));
			}
			printf(".\n");
		}
		if (ioaddr != port_base) { 				/* WD emulation. */
			printf("WD8013 compatible registers: ");
			for(i = 0; i < 16; i++) {
				printf(" %2.2x", inb(port_base + i));
				fflush(stdout);
			}
			printf(".\n");
		}
	}

	printf("Reading the configuration from the AT/LANTIC at %#3x...\n",
		   port_base);
	outb(0x21, ioaddr);			/* Select Window 0 */
	old_regA = regA = inb(ioaddr + 0x0A);
	old_regB = regB = inb(ioaddr + 0x0B);

	printf("The current configuration is\n");
	show_config(regA, regB);

	/* This should never be triggered.  We see the card after all!*/
	if ((regA & 7) == 1)
		printf(" Your card is set to be configured at boot-time.\n"
			   "  Remaining with this software configuration is a bad idea"
			   " when using\n  modern systems.  The card will likely be"
			   " unintentionally activated at\n  unexpected I/O and IRQ"
			   " settings.\n" );

	if (new_mode)
		regA = (regA & ~0x80) | (new_mode == 1  ? 0x80 : 0);
	if (new_irq >= 0)
		regA = (regA & ~0x38) | (irq2index[new_irq]<<3);
	if (new_io_idx >= 0)
		regA = (regA & ~0x07) | new_io_idx;
	if (xcvr >= 0)
		regB = (regB & 0x5C) | (xcvr & 0x3);

	if (regA != old_regA || regB != old_regB || write_eeprom) {
		printf("The proposed new configuration is\n");
		show_config(regA, regB);
		if ( ! write_eeprom)
			printf(" Use '-w' to set this as the current configuration "
				   "(valid until next reset).\n"
				   " Use '-w -w' to write the settings to the EEPROM.\n"
				   " Use '-w -w -w' to do both.\n");
	}

	/* We must write the EEPROM and register B before possibly moving the
	   I/O base. */
	if (write_eeprom > 1) {
		printf("Writing the new configuration to the EEPROM...\n");
		write_EEPROM(ioaddr, regA, regB, 0x30);
		printf("Wrote the EEPROM at %#3x with 0x%2.2x 0x%2.2x 0x%2.2x.\n",
			   port_base, regA, regB, 0x30);
	}

	if (write_eeprom & 1) {
		if (regB != old_regB) {
			printf(" Setting register B to %#02x.\n", regB);
			inb(ioaddr + 0x0B);
			outb(regB, ioaddr + 0x0B);
			regB = inb(ioaddr + 0x0B);
			printf(" Register B is now %#02x:  Interface %s.\n", regB,
				   xcvr_name[regB & 0x03]);
		}
		if (regA != old_regA) {
			printf(" Setting register A to %#02x (%s mode I/O %#x IRQ %d).\n",
				   regA, regA & 0x80 ? "WD8013" : "NE2000",
				   io_base[regA & 0x07], index2irq[(regA >> 3) & 0x07] );
			inb(ioaddr + 0x0A);
			outb(regA, ioaddr + 0x0A);
			if (port_base == io_base[regA&7]) {
				ioaddr = port_base + (regA & 0x80 ? 16 : 0);
				printf(" Register A at base %x is now %#02x.\n",
					   ioaddr, inb(ioaddr + 0x0A));
			}
		}
	}

	return 0;
}

static void show_config(int regA, int regB)
{
	printf("  Register A 0x%2.2x:  I/O base @ %#x, IRQ %d,"
		   " %s mode, %s ISA read.\n",
		   regA, io_base[regA & 0x07], index2irq[(regA >> 3) & 0x07],
		   regA & 0x80 ? "WD8013" : "NE2000",
		   regA & 0x40 ? "fast" : "normal" );
	printf("  Register B 0x%2.2x:  Interface %s.\n", regB,
		   xcvr_name[regB & 0x03]);
	printf("    Boot PROM writes are %sabled, CHRDY after %s%s.\n",
		   regB & 0x40 ? "en" : "dis",
		   regB & 0x10 ? "BALE" : "IORD/IOWR",
		   regB & 0x08 ? ", IO16 after IORD/IOWR" : "");
	if (regB & 0x20)
		printf("    Bus error detected.\n");
}

/* Write the A, B, and C configuration registers into the EEPROM at
   DP8390_BASE.
   Ideally this would be done with interrupts disabled, but we do not have
   that capability with user level code.
*/
static void write_EEPROM(int dp8390_base, int regA, int regB, int regC)
{
	int ioregb = dp8390_base + 0x0B;
	int currB = inb(ioregb) & (~0x04) ;

	outb(currB | 0x80, ioregb);
	inb(ioregb);
	outb(regA, ioregb);
	outb(regB, ioregb);
	outb(regC, ioregb);
	return;
}

/*
 * Local variables:
 *  compile-command: "gcc -Wall -Wstrict-prototypes -O6 -o atlantic atlantic.c"
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 * End:
 */
