/*  ne2k-diag.c: Diagnostic program for NE2000 ethercards. */

static const char version_msg[] =
"ne2k-diag.c:v2.02 2/12/2001 Donald Becker (becker@scyld.com)\n";
static const char usage_msg[] =
"\t[Short] [Long arg] [Options]  - [definition]\n"
"\t-a  --show-registers      - show chip status (-aa for more)\n"
"\t-D  --debug               - describe program internal state\n"
"\t-e  --show-eeprom         - show EEPROM contents (-ee for more)\n"
"\t-f  --force               - continue past detection errors\n"
"\t-p  --base-address 0xNNN  - specify card base address\n"
"\t-v  --verbose             - verbose mode\n"
"\t-w  --write               - commit the new values to the EEPROM\n"
"\t-h  --help                - display this usage message\n"
"\t-V  --version             - display version\n\n"
"\t-P  --new-address  0xNNN  - new card base address\n"
"\t-Q  --new-irq      IRQ    - new card IRQ\n"
"\t-X  --new-xcvr     N      - new transceiver index.\n"
;

/*
	Written 1993,1994,1999-2004 by Donald Becker.
	Copyright 1994,1999-2001 Donald Becker
	Copyright 1993 assigned to 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 v2.0 (GPL), incorporated herein by
	reference. For the full text see
	 http://scyld.com/expert/license.html
	No substitution of license terms are permitted.

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

	This program is used to diagnose detection ("probe") problems with NE1000
	and NE2000 work-alike cards.   The probe routine is very similar to that
	used in the Linux NE2000 driver, and should work with any NE2000
	clone.

	See the bottom of this file for the compile command.
	Do not forget the "-O"!

	Some of the names and comments for the #defines came from Crynwr.

	1999/Mar/18 James Bourne <jbourne@affinity-systems.ab.ca>
	Fixed -h --help and -V --version flags
	References
      Realtek RTL8019 datasheet, http://www.realtek.com.tw/cn/cn.html
      Davicom DM8009 (nee UMC-9008) datasheet http://www.davicom.com.tw/
*/

#ifndef __OPTIMIZE__
#warning  You must compile this program with the correct options!
#warning  See the last lines of the source file for the suggested command.
#error You must compile this driver with "-O".
#endif

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

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



#define printk printf

#define NS_CMD	 (dev->base_addr)
#define NS_BASE	 (dev->base_addr)
#define NS_DATAPORT		0x10	/* NatSemi-defined port window offset. */
#define NE_DATAPORT		0x10	/* NatSemi-defined port window offset. */
#define NS_RESET		0x1f	/* Issue a read to reset, a write to clear. */

#define NE1SM_START_PG	0x20	/* First page of TX buffer */
#define NE1SM_STOP_PG	0x40	/* Last page +1 of RX ring */
#define NESM_START_PG	0x40	/* First page of TX buffer */
#define NESM_STOP_PG	0x80	/* Last page +1 of RX ring */

#define E8390_CMD		0x00	/* The command register (for all pages) */
#define E8390_STOP		0x01	/* Stop and reset the chip */
#define E8390_START		0x02	/* Start the chip, clear reset */
#define E8390_RREAD		0x08	/* Remote read */
#define E8390_NODMA		0x20	/* Remote DMA */
#define E8390_PAGE0		0x00	/* Select page chip registers */
#define E8390_PAGE1		0x40	/* using the two high-order bits */
#define E8390_PAGE2		0x80
#define E8390_PAGE3		0xC0	/* Page 3 is invalid on the real 8390. */

#define E8390_RXOFF 0x20		/* EN0_RXCR: Accept no packets */
#define E8390_TXOFF 0x02		/* EN0_TXCR: Transmitter off */

/* Page 0 register offsets. */
#define EN0_CLDALO		0x01	/* Low byte of current local dma addr  RD */
#define EN0_STARTPG		0x01	/* Starting page of ring bfr WR */
#define EN0_CLDAHI		0x02	/* High byte of current local dma addr	RD */
#define EN0_STOPPG		0x02	/* Ending page +1 of ring bfr WR */
#define EN0_BOUNDARY	0x03	/* Boundary page of ring bfr RD WR */
#define EN0_TSR			0x04	/* Transmit status reg RD */
#define EN0_TPSR		0x04	/* Transmit starting page WR */
#define EN0_NCR			0x05	/* Number of collision reg RD */
#define EN0_TCNTLO		0x05	/* Low	byte of tx byte count WR */
#define EN0_FIFO		0x06	/* FIFO RD */
#define EN0_TCNTHI		0x06	/* High byte of tx byte count WR */
#define EN0_ISR			0x07	/* Interrupt status reg RD WR */
#define EN0_CRDALO		0x08	/* low byte of current remote dma address RD */
#define EN0_RSARLO		0x08	/* Remote start address reg 0 */
#define EN0_CRDAHI		0x09	/* high byte, current remote dma address RD */
#define EN0_RSARHI		0x09	/* Remote start address reg 1 */
#define EN0_RCNTLO		0x0a	/* Remote byte count reg WR */
#define EN0_RCNTHI		0x0b	/* Remote byte count reg WR */
#define EN0_RSR			0x0c	/* rx status reg RD */
#define EN0_RXCR		0x0c	/* RX configuration reg WR */
#define EN0_TXCR		0x0d	/* TX configuration reg WR */
#define EN0_COUNTER0	0x0d	/* Rcv alignment error counter RD */
#define EN0_DCFG		0x0e	/* Data configuration reg WR */
#define EN0_COUNTER1	0x0e	/* Rcv CRC error counter RD */
#define EN0_IMR			0x0f	/* Interrupt mask reg WR */
#define EN0_COUNTER2	0x0f	/* Rcv missed frame error counter RD */


void write_EEPROM(int port_base, int regA, int regB, int regC);
static int do_probe(long ioaddr);
static void rtl8019(long ioaddr);
static void asix_88190(long ioaddr);
static void dm9008(long ioaddr);
int mdio_read(long ioaddr, int phy_id, int location);

int opt_a = 0, opt_f = 0, debug = 0, show_eeprom = 0, opt_F = 0, new_irq = 0;
int verbose = 0;
int show_mii = 0;
int new_port_base = 0;
int interface = -1;
int do_write_eeprom = 0;

struct option longopts[] = {
 /* { name	has_arg	 *flag	val } */
	{"show-registers",	0, 0, 'a'},
	{"debug",			0, 0, 'D'},
	{"show-eeprom",		0, 0, 'e'},
	{"force",			0, 0, 'f'},
	{"base-address",	1, 0, 'p'}, /* Base address */
	{"help",			0, 0, 'h'},	/* Give help */
	{"mii",				0, 0, 'm'},	/* Show MII registers */
	{"verbose",			0, 0, 'v'},	/* Verbose mode */
	{"version",			0, 0, 'V'},	/* Display version number */
	{"interface",		1, 0, 'F'},	/* New transceiver index (10baseT, AUI) */
	{"new-address",		1, 0, 'P'}, /* Base address */
	{"new-irq",			1, 0, 'Q'},	/* New interrupt number */
	{ 0, 					0, 0, 0 }
};


/*	Probe for various non-shared-memory ethercards.

   NEx000-clone boards have a Station Address PROM (SAPROM) in the packet
   buffer memory space.	 NE2000 clones have 0x57,0x57 in bytes 0x0e,0x0f of
   the SAPROM, while other supposed NE2000 clones must be detected by their
   SA prefix.

   Reading the SAPROM from a word-wide card with the 8390 set in byte-wide
   mode results in doubled values, which can be detected and compensated for.

   The probe is also responsible for initializing the card and filling
   in the 'dev' and 'ei_status' structures.

   We use the minimum memory size for some ethercard product lines, iff we can't
   distinguish models.	You can increase the packet buffer size by setting
   PACKETBUF_MEMSIZE.  Reported Cabletron packet buffer locations are:
		E1010	starts at 0x100 and ends at 0x2000.
		E1010-x starts at 0x100 and ends at 0x8000. ("-x" means "more memory")
		E2010	starts at 0x100 and ends at 0x4000.
		E2010-x starts at 0x100 and ends at 0xffff.	 */

int main(int argc, char *argv[])
{
	int show_version = 0, errflag = 0;
	int port_base = 0x300;
	int c;
	extern char *optarg;
	int option_index = 0;

	while ((c = getopt_long(argc,argv, "aDefF:i:mp:P:Q:svwX:Vh",
							longopts, &option_index))
		!= -1) {
		switch (c) {
		case 'a': opt_a++; break;
		case 'D': debug++; break;
		case 'e': show_eeprom++; break;
		case 'f': opt_f++; break;
		case 'F':
		case 'h':  printf(usage_msg); return 0;
		case 'm': show_mii++; break;
		case 'X': opt_F++; interface = atoi(optarg); break;
		case 'p': port_base = strtol(optarg, NULL, 16); break;
		case 'P': new_port_base = strtol(optarg, NULL, 16); break;
		case 'Q':
			new_irq = atoi(optarg);
			/* On ISA IRQ2 == IRQ9, with IRQ9 the proper name. */
			if (new_irq < 2 || 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 'v': verbose++;	 break;
		case 'V': show_version++;		 break;
		case 'w': do_write_eeprom++;	break;
		case '?':
			errflag++; break;
		}
	}
	if (errflag) {
		fprintf(stderr, usage_msg);
		return 3;
	}

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

	if (ioperm(port_base, 32, 1)) {
		perror("ne2k-diag: ioperm()");
		fprintf(stderr, "This program must be run as root.\n");
		return 2;
	}
	/* The following is needed for SLOW_DOWN_IO. */
	if (ioperm(0x80, 1, 1)) {
		perror("ioperm");
		return 1;
	}

	printf("Checking the ethercard at %#3x.\n", port_base);
	{	int regd;
		long ioaddr = port_base;

		outb_p(E8390_NODMA+E8390_PAGE1+E8390_STOP, ioaddr + E8390_CMD);
		regd = inb_p(ioaddr + 0x0d);
		printk("  Receive alignment error counter (%#lx) is %2.2x\n",
			   ioaddr + 0x0d, regd);
		outb_p(0xff, ioaddr + 0x0d);
		outb_p(E8390_NODMA+E8390_PAGE0, ioaddr + E8390_CMD);
		inb_p(ioaddr + EN0_COUNTER0); /* Clear the counter by reading. */
		if (inb_p(ioaddr + EN0_COUNTER0) != 0) {
			outb(regd, ioaddr + 0x0d);	/* Restore the old values. */
			printk("  Failed initial NE2000 probe, value %2.2x.\n",
				   inb(ioaddr + EN0_COUNTER0));
		} else
			printk("  Passed initial NE2000 probe, value %2.2x.\n",
				   inb(ioaddr + EN0_COUNTER0));

	}

	do_probe(port_base);
	return 0;
}

static int do_probe(long ioaddr)
{
	int i;
	int neX000, ctron, dlink;
	unsigned char SA_prom[32];
	int wordlength = 2;
	char *name;
	int start_page = NESM_START_PG, stop_page = NESM_STOP_PG;

	struct {char value, offset; } program_seq[] = {
		{E8390_NODMA+E8390_PAGE0+E8390_STOP, E8390_CMD}, /* Select page 0*/
		{0x48,	EN0_DCFG},		/* Set byte-wide (0x48) access. */
		{0x00,	EN0_RCNTLO},	/* Clear the count regs. */
		{0x00,	EN0_RCNTHI},
		{0x00,	EN0_IMR},		/* Mask completion irq. */
		{0xFF,	EN0_ISR},
		{E8390_RXOFF, EN0_RXCR},		/* 0x20	 Set to monitor */
		{E8390_TXOFF, EN0_TXCR},		/* 0x02	 and loopback mode. */
		{32,			EN0_RCNTLO},
		{0x00,	EN0_RCNTHI},
		{0x00,	EN0_RSARLO},	/* DMA starting at 0x0000. */
		{0x00,	EN0_RSARHI},
		{E8390_RREAD+E8390_START, E8390_CMD},
	};

	/* Read the 16 bytes of station address prom, returning 1 for
	   an eight-bit interface and 2 for a 16-bit interface.
	   We must first initialize registers, similar to NS8390_init(eifdev, 0).
	   We can't reliably read the SAPROM address without this.
	   (I learned the hard way!). */
	for (i = 0; i < sizeof(program_seq)/sizeof(program_seq[0]); i++)
		outb_p(program_seq[i].value, ioaddr + program_seq[i].offset);

	for(i = 0; i < 32 /*sizeof(SA_prom)*/; i+=2) {
		SA_prom[i] = inb_p(ioaddr + NE_DATAPORT);
		SA_prom[i+1] = inb_p(ioaddr + NE_DATAPORT);
		if (SA_prom[i] != SA_prom[i+1])
			wordlength = 1;
	}

	printk("Station Address PROM    0:");
	for(i = 0; i < sizeof(SA_prom)/2; i++)
		printk(" %2.2x", SA_prom[i]);
	printk("\nStation Address PROM %#2x:", i);
	for(; i < sizeof(SA_prom); i++)
		printk(" %2.2x", SA_prom[i]);
	printk("\n");

	if (wordlength == 2) {
		/* We must set the 8390 for word mode, AND RESET IT. */
		int tmp;
		outb_p(0x49, ioaddr + EN0_DCFG);
		tmp = inb_p(ioaddr + NS_RESET);
		outb(tmp, ioaddr + NS_RESET);
		/* Un-double the SA_prom values. */
		for (i = 0; i < 16; i++)
			SA_prom[i] = SA_prom[i+i];
	}

	neX000 =  (SA_prom[14] == 0x57	&&	SA_prom[15] == 0x57);
	ctron =	 (SA_prom[0] == 0x00 && SA_prom[1] == 0x00 && SA_prom[2] == 0x1d);
	dlink =	 (SA_prom[0] == 0x00 && SA_prom[1] == 0xDE && SA_prom[2] == 0x01);

	/* Set up the rest of the parameters. */
	if (neX000 || dlink) {
		if (wordlength == 2) {
			name = dlink ? "DE200" : "NE2000";
			start_page = NESM_START_PG;
			stop_page = NESM_STOP_PG;
		} else {
			name = dlink ? "DE100" : "D-Link";
			start_page = NE1SM_START_PG;
			stop_page = NE1SM_STOP_PG;
		}
	} else if (ctron) {
		name = "Cabletron";
		start_page = 0x01;
		stop_page = (wordlength == 2) ? 0x40 : 0x20;
	} else {
		printk(" Invalid signature found, wordlength %d.\n", wordlength);
		if (! opt_f)
			return 1;
		name = "NE2000 (invalid signature)";
		
	}

	printk("  %s found at %#lx, using start page %#x and end page %#x.\n",
		   name, ioaddr, start_page, stop_page);

	outb_p(E8390_NODMA + E8390_PAGE1, ioaddr + E8390_CMD);
	printf("The current MAC stations address is ");
	for (i = 1; i < 6; i++)
		printf("%2.2X:", inb(ioaddr + i));
	printf("%2.2X.\n", inb(ioaddr + i));
	
	if (opt_a) {
		int page; 
		for (page = 0; page < 4; page++) {
			printf("8390 page %d:", page);
			outb_p(E8390_NODMA + (page << 6), ioaddr + E8390_CMD);
			for(i = 0; i < 16; i++)
				printf(" %2.2x", inb_p(ioaddr + i));
			printf(".\n");
		}
	}

	outb_p(E8390_NODMA + E8390_PAGE0, ioaddr + E8390_CMD);
	/* Check for a RealTek 8019 chip. */
	if (inb(ioaddr + 10) == 'P'  &&  inb(ioaddr + 11) == 'p')
		rtl8019(ioaddr);
	else if (show_mii)			/* XXX Should be a AX88190 check */
		asix_88190(ioaddr);

	if (show_mii) {
		int phys[4], phy, phy_idx = 0;
		printf("The MII I/O register is %2.2x.\n", inb(ioaddr + 0x14));
		for (phy = 0; phy < 32 && phy_idx < 4; phy++) {
			int mii_status = mdio_read(ioaddr, phy, 1);
			if (mii_status != 0xffff   &&  mii_status != 0x0000) {
				phys[phy_idx++] = phy;
				printf(" MII PHY found at address %d, status 0x%4.4x.\n",
					   phy, mii_status);
			}
		}
		if (phy_idx == 0)
			printf(" ***WARNING***: No MII transceivers found!\n");
		for (phy = 0; phy < phy_idx; phy++) {
			int mii_reg;
			printf(" MII PHY #%d transceiver registers:", phys[phy]);
			for (mii_reg = 0; mii_reg < 32; mii_reg++)
				printf("%s %4.4x", (mii_reg % 8) == 0 ? "\n  " : "",
					   mdio_read(ioaddr, phys[phy], mii_reg));
			printf(".\n");
		}
#ifdef LIBMII
		show_mii_details(ioaddr, phys[0]);
		if (show_mii > 1)
			monitor_mii(ioaddr, phys[0]);
#endif
	}
	return 0;
}

/* Serial EEPROM section. */
/* The description of EEPROM access. */
struct ee_ctrl_bits {
	int offset;
	unsigned char shift_clk,	/* Bit that drives SK (shift clock) pin */
		read_bit,				/* Mask bit for DO pin value */
		write_0, write_1,		/* Enable chip and drive DI pin with 0 / 1 */
		disable;				/* Disable chip. */
};

/* The EEPROM commands include the alway-set leading bit. */
enum EEPROM_Cmds { EE_WriteCmd=5, EE_ReadCmd=6, EE_EraseCmd=7, };

/* This executes a generic EEPROM command, typically a write or write enable.
   It returns the data output from the EEPROM, and thus may also be used for
   reads and EEPROM sizing. */
static int do_eeprom_cmd(struct ee_ctrl_bits *ee, long ioaddr, int cmd,
						 int cmd_len)
{
	long ee_addr = ioaddr + ee->offset;
	unsigned retval = 0;

	if (debug > 1)
		printf(" EEPROM op 0x%x: ", cmd);

	/* Shift the command bits out. */
	do {
		short dataval = (cmd & (1 << cmd_len)) ? ee->write_1 : ee->write_0;
		outb(dataval, ee_addr);
		if (debug > 2)
			printf("%X", inb(ee_addr) & 15);
		outb(dataval |ee->shift_clk, ee_addr);
		retval = (retval << 1) | ((inb(ee_addr) & ee->read_bit) ? 1 : 0);
	} while (--cmd_len >= 0);
	outb(ee->write_0, ee_addr);

	/* Terminate the EEPROM access. */
	outb(ee->disable, ee_addr);
	if (debug > 1)
		printf(" EEPROM result is 0x%5.5x.\n", retval);
	return retval;
}

/* Wait for the EEPROM to finish what it is doing. */
static int eeprom_busy_poll(struct ee_ctrl_bits *ee, long ioaddr)
{
	int ee_addr = ioaddr + ee->offset;
	int i;

	outb(ee->write_0, ee_addr);
	for (i = 0; i < 10000; i++)			/* Typical 2000 ticks */
		if (inb(ee_addr) & ee->read_bit)
			break;
	return i;
}

/* The abstracted functions for EEPROM access. */
static int read_eeprom(struct ee_ctrl_bits *ee, long ioaddr, int location)
{
	int addr_len = 6;
	return do_eeprom_cmd(ee, ioaddr, ((EE_ReadCmd << addr_len) | location) << 16,
						 3 + addr_len + 16) & 0xffff;
}

static void write_eeprom(struct ee_ctrl_bits *eebits, long ioaddr, int index,
						 int value)
{
	int addr_len = 6;
	int i;

	/* Poll for previous op finished. */
	eeprom_busy_poll(eebits, ioaddr);

	/* Enable programming modes. */
	do_eeprom_cmd(eebits, ioaddr, (0x4f << (addr_len-4)), 3 + addr_len);
	/* Do the actual write. */
	do_eeprom_cmd(eebits, ioaddr,
				  (((EE_WriteCmd<<addr_len) | index)<<16) | (value & 0xffff),
				  3 + addr_len + 16);
	i = eeprom_busy_poll(eebits, ioaddr);
	if (debug)
		printf(" Write finished after %d ticks.\n", i);
	/* Disable programming.  Note: this command is not instantaneous, but
	   we check for busy before the next write. */
	do_eeprom_cmd(eebits, ioaddr, (0x40 << (addr_len-4)), 3 + addr_len);
}



/* The RealTek RTL8019 specific routines. */
static const char rtl_irqmap[] = { 9, 3, 4, 5, 10, 11, 12, 15 };
static const char rtl_irq2config_map[16] = {
	-1,-1,0x00,0x10,  0x20,0x30,-1,-1,  -1,0x00,0x40,0x50, 0x60,-1,-1,0x70};
static const int iomap[] = {
	0x300, 0x320, 0x340, 0x360,  0x380, 0x3A0, 0x3C0, 0x3E0,
	0x200, 0x220, 0x240, 0x260,  0x280, 0x2A0, 0x2C0, 0x2E0 };
static const char *const xcvr_modes[4] = {
	"10baseT or coax, selected on 10baseT link beat",
	"10baseT with link test disabled", "10base5 / AUI", "10base2"};
struct ee_ctrl_bits rtl_ee_tbl = {0x01,  0x04, 0x01, 0x88, 0x8A, 0x00 };
#define EEPROM_SIZE 64

static void rtl8019(long ioaddr)
{
	int config0, config1, config2, config3, eeword0, eeword1;
	int i;

	/* The rtl8019-specific registers are on page 3. */  
	outb(E8390_NODMA+E8390_PAGE3, ioaddr + E8390_CMD);
	/* We do not use symbolic names for the Realtek-specific registers. */
	config0 = inb(ioaddr + 3);
	config1 = inb(ioaddr + 4);
	config2 = inb(ioaddr + 5);
	config3 = inb(ioaddr + 6);
	printf("This is a RTL8019%s chip in jumper%s%s mode.\n",
		   config0 & 0xC0 ? "" : "AS", config0 & 0x08 ? "ed" : "less",
		   (config0 & 0x08) == 0 && (config3 & 0x80) ? " PnP" :"");
	printf("  The current settings are: IRQ %d (%sabled, I/O address 0x%x,\n",
		   rtl_irqmap[(config1>>4) & 7], config1 & 0x80 ? "en" : "dis",
		   iomap[config1 & 15]);
	printf("     The transceiver is set to %s,\n"
		   "     The chip is set to %s duplex.\n",
		   xcvr_modes[config2 >> 6], config3 & 0x40 ? "full" : "half");
	eeword0 = read_eeprom(&rtl_ee_tbl, ioaddr, 0);
	eeword1 = read_eeprom(&rtl_ee_tbl, ioaddr, 1);
	config1 = eeword0;
	config2 = eeword0 >> 8;
	config3 = eeword1;
	printf("  The EEPROM settings are: IRQ %d, I/O address 0x%x, %sPnP mode.\n"
		   "     The transceiver is set to %s,\n"
		   "     The chip is set to %s duplex.\n",
		   rtl_irqmap[(config1>>4) & 7], iomap[config1 & 15],
		   (config3 & 0x80) ? "" :"non-",
		   xcvr_modes[config2 >> 6], config3 & 0x40 ? "full" : "half");
	if (show_eeprom) {
		unsigned short sum = 0, val;
		int i;
		printf("EEPROM contents:");
		for (i = 0; i < EEPROM_SIZE; i++) {
			val = read_eeprom(&rtl_ee_tbl, ioaddr, i);
			printf("%s %4.4x", (i & 7) == 0 ? "\n ":"", val);
			sum += val;
		}
		printf("\n The word-wide EEPROM checksum is %#4.4x.\n", sum);
	}
	if (opt_F  ||  new_irq > 0  ||  new_port_base > 0) {
		int newval0 = eeword0;
		if (opt_F  &&  interface >= 0  &&  interface < 4) {
			newval0 &= 0x3fff;
			newval0 |= interface << 14;
		}
		if (new_irq > 0 && new_irq < 16 && rtl_irq2config_map[new_irq] > 0) {
			newval0 &= 0xff8f;
			newval0 |= rtl_irq2config_map[new_irq];
		}

		for (i = 0; i < 16; i++)
			if (iomap[i] == new_port_base) {
				newval0 &= 0xfff0;
				newval0 |= i;
				break;
			}
		if (do_write_eeprom) {
			printf(" Writing 0x%4.4x to EEPROM word 0.\n", newval0);
			write_eeprom(&rtl_ee_tbl, ioaddr, 0, newval0);
		} else
			printf(" Would write 0x%4.4x to EEPROM word 0 with '-w'.\n",
				   newval0);
	}
}


/* The ASIX 88190 commands. */
struct ee_ctrl_bits asix_ee_tbl = {0x14,  0x80, 0x40, 0x10, 0x30, 0x00 };
static void asix_88190(long ioaddr)
{
	if (show_eeprom) {
		unsigned short sum = 0, val;
		int i;
		printf("ASIX 88190 EEPROM contents:");
		for (i = 0; i < EEPROM_SIZE; i++) {
			val = read_eeprom(&asix_ee_tbl, ioaddr, i);
			printf("%s %4.4x", (i & 7) == 0 ? "\n ":"", val);
			sum += val;
		}
		printf("\n The word-wide EEPROM checksum is %#4.4x.\n", sum);
	}
}


/* Davicom / UMC 9008 chips. */
static const char dm_irqmap[8] = { 3, 4, 5, 9, 10, 11, 12, 15 };
struct ee_ctrl_bits dm_ee_tbl = {
	offset: 0x07,
	shift_clk: 0x04, read_bit: 0x01, write_0: 0x88, write_1: 0x8A,
	disable: 0x00 };
static const char *const dm_xcvr_mode[4] = {
	"10baseT with link test disabled", "10base2",
	"10base5 / AUI", "10baseT or AUI, selected on 10baseT link beat", };
static const char dm_rom_size[16] = {
	 0,  0,  16, 16,  16, 16, 16, 16,
	 16, 16, 32, 32,  32, 32, 64, 64, };
static const signed char dm_rom_base[16] = {
	0, 0, 0xC0, 0xC4,  0xC8, 0xCC, 0xD0, 0xD4,
	0xD8, 0xDC, 0xC0, 0xC8,  0xD0, 0xD8, 0xC0, 0xD0, };

static void dm9008(long ioaddr)
{
	unsigned short sum = 0, ee_vals[EEPROM_SIZE];
	int i, configBA, config1C;
	/* The dm9008 EEPROM registers is on page 3. */  
	outb(E8390_NODMA+E8390_PAGE3, ioaddr + E8390_CMD);
	for (i = 0; i < EEPROM_SIZE; i++) {
		ee_vals[i] = read_eeprom(&dm_ee_tbl, ioaddr, i);
		sum += ee_vals[i];
	}
	printf("Davicom / UMC 9008 EEPROM contents:");
	for (i = 0; i < EEPROM_SIZE; i++) {
		if ((i&15) == 0)
			printf("\n0x%3.3x: ", i + 0x100);
		printf(" %2.2x", ee_vals[i]);
	}
	printf("\n The word-wide EEPROM checksum is %#4.4x.\n", sum);
	printf("  The EEPROM station address is "
		   "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
		   ee_vals[0] & 0xff, ee_vals[0] >> 8,
		   ee_vals[1] & 0xff, ee_vals[1] >> 8,
		   ee_vals[2] & 0xff, ee_vals[2] >> 8);
	configBA = ee_vals[14];
	config1C = ee_vals[15];
	printf("  The EEPROM settings are: IRQ %d, I/O address 0x%x.\n"
		   "     The transceiver is set to %s, link status: %s.\n",
		   dm_irqmap[(configBA>>4) & 7], iomap[configBA & 15],
		   dm_xcvr_mode[(configBA >> 8) & 3],
		   configBA & 0x0400 ? "link beat detected" : "no link beat");
	printf("   %dK boot ROM at address %#2.2x000.\n",
		   dm_rom_size[config1C & 0x0f], dm_rom_base[config1C & 0x0f]);
	printf(" Vendor ID is %4.4x %4.4x, serial # %4.4x %4.4x\n",
		   ee_vals[17], ee_vals[16], ee_vals[19], ee_vals[18]);
}

/* MII - MDIO  (Media Independent Interface - Management Data I/O) accesses. */

/* Read and write the MII registers using software-generated serial
   MDIO protocol.  It is just different enough from the serial EEPROM protocol
   to not share code.  The maxium data clock rate is 2.5 Mhz. */
#define mdio_delay(mdio_addr)	/* No delay is needed with ISA or PCMCIA. */
#define MDIO_IO_OFFSET 0x14

#define MDIO_SHIFT_CLK	0x01
#define MDIO_DATA_BIT	0x04
/* Derived values. */
#define MDIO_DATA_WRITE0 0x00
#define MDIO_DATA_WRITE1 0x08
#define MDIO_ENB		0x00
#define MDIO_ENB_IN		0x02
#define MDIO_DATA_READ	0x04

/* Syncronize the MII management interface by shifting 32 one bits out. */
static void mdio_sync(long ioaddr)
{
	long mdio_addr = ioaddr + MDIO_IO_OFFSET;
	int i;

	for (i = 32; i >= 0; i--) {
		outb(MDIO_ENB | MDIO_DATA_WRITE1, mdio_addr);
		mdio_delay(mdio_addr);
		outb(MDIO_ENB | MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK, mdio_addr);
		mdio_delay(mdio_addr);
	}
	return;
}

int mdio_read(long ioaddr, int phy_id, int location)
{
	long mdio_addr = ioaddr + MDIO_IO_OFFSET;
	int i;
	int read_cmd = (0xf6 << 10) | (phy_id << 5) | location;
	int retval = 0;

	if (verbose > 2)		/* Debug: 5 */
		printf(" mdio_read(%#lx, %d, %d)..", ioaddr, phy_id, location);
	/* Establish sync by sending at least 32 logic ones. */
	mdio_sync(ioaddr);
	/* Shift the read command bits out. */
	for (i = 17; i >= 0; i--) {
		int dataval = (read_cmd & (1 << i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
		if (verbose > 3)		/* Debug: 5 */
			printf("%d", (read_cmd & (1 << i)) ? 1 : 0);

		outb(MDIO_ENB | dataval, mdio_addr);
		mdio_delay(mdio_addr);
		outb(MDIO_ENB | dataval | MDIO_SHIFT_CLK, mdio_addr);
		if (verbose > 3) printf(" %x", (inb(mdio_addr) >> 16) & 0x0f);
		mdio_delay(mdio_addr);
	}
	if (verbose > 3) printf("-> %x", (inb(mdio_addr) >> 16) & 0x0f);

	/* Read the two transition, 16 data, and wire-idle bits. */
	for (i = 19; i > 0; i--) {
		outb(MDIO_ENB_IN, mdio_addr);
		mdio_delay(mdio_addr);
		retval = (retval << 1) | ((inb(mdio_addr) & MDIO_DATA_READ) ? 1 : 0);
		if (verbose > 3) printf(" %x", (inb(mdio_addr) >> 16) & 0x0f);
		outb(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr);
		mdio_delay(mdio_addr);
	}
	if (verbose > 3) printf(" == %4.4x.\n", retval);
	return (retval>>1) & 0xffff;
}

void mdio_write(long ioaddr, int phy_id, int location, int value)
{
	long mdio_addr = ioaddr + MDIO_IO_OFFSET;
	int i;
	int cmd = (0x5002 << 16) | (phy_id << 23) | (location<<18) | value;

	/* Establish sync by sending 32 logic ones. */
	mdio_sync(ioaddr);
	/* Shift the command bits out. */
	for (i = 31; i >= 0; i--) {
		int dataval = (cmd & (1 << i)) ? MDIO_DATA_WRITE1 : 0;
		outb(MDIO_ENB | dataval, mdio_addr);
		mdio_delay(mdio_addr);
		outb(MDIO_ENB | dataval | MDIO_SHIFT_CLK, mdio_addr);
		mdio_delay(mdio_addr);
	}
	/* Clear out extra bits. */
	for (i = 2; i > 0; i--) {
		outb(MDIO_ENB_IN, mdio_addr);
		mdio_delay(mdio_addr);
		outb(MDIO_ENB_IN | MDIO_SHIFT_CLK, mdio_addr);
		mdio_delay(mdio_addr);
	}
	return;
}


/*
 * Local variables:
 *  compile-command: "gcc -Wall -O6 -o ne2k-diag ne2k-diag.c"
 *  tab-width: 4
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
