/* at1700.c: Diagnostic program for the Allied Telesis AT1700.

   Written 1993,1994 by Donald Becker.  No redistribution permitted.
   (This code will be GPL, copyright DIRNSA when released -- the same
   as the Linux ethercard drivers.)

   The Author may be reached as becker@super.org or
   C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715

   This is a diagnostic program for the Allied Telesis AT1700, and similar
   boards built around the Fujitsu MB86965.   Don't expect it
   to test every possible hardware fault, but it does verify the basic
   functionality of the board.  I wrote this primarily to get hands-on
   experience to understand how the hardware works.
*/

static char *version =
    "at1700.c:v0.02 2/9/94 Donald Becker (becker@super.org)\n";

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <asm/io.h>
#include <sys/time.h>
#include <linux/in.h>
#include <linux/if_ether.h>

#include </usr/src/linux/drivers/net/iow.h>

/* Offsets from the base address. */
#define EEPROM_Ctrl 	16
#define EEPROM_Data 	17

/*  EEPROM_Ctrl bits. */
#define EE_SHIFT_CLK	0x40	/* EEPROM shift clock, in reg. 16. */
#define EE_CS			0x20	/* EEPROM chip select, in reg. 16. */
#define EE_DATA_WRITE	0x80	/* EEPROM chip data in, in reg. 17. */
#define EE_DATA_READ	0x80	/* EEPROM chip data out, in reg. 17. */

/* Delay between EEPROM clock transitions. */
#define eeprom_delay()	do { int _i = 40; while (--_i > 0) { __SLOW_DOWN_IO; }} while (0)

/* The EEPROM commands include the alway-set leading bit. */
#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. */
    {"dump",       0, 0, 'd'},	/* Dump the first page. */
    {"examine",    1, 0, 'e'},	/* Examine a specified page. */
    {"help",       0, 0, 'h'},	/* Give help */
    {"interface",  0, 0, 'f'},	/* Interface number (built-in, AUI) */
    {"irq",	   	   1, 0, 'i'},	/* Interrupt number */
    {"reset ASIC", 0, 0, 'r'},	/* Reset the ASIC before doing anything. */
    {"verbose",    0, 0, 'v'},	/* Verbose mode */
    {"version",    0, 0, 'V'},	/* Display version number */
    {"write-EEPROM", 1, 0, 'w'},/* Write the EEPROM with the specified vals */
    { 0, 0, 0, 0 }
};

#define printk printf

int verbose = 0;

struct device {
    char *name;
    short base_addr;
    unsigned char irq;
    unsigned char if_port;
    unsigned char dev_addr[6];	/* Hardware station address. */
    unsigned int tbusy:1, interrupt:1, start:1;
	void *priv;
} devs, *dev = &devs;

struct device *irq2dev_map[16] = {0, /* ... */};

int net_debug = 7;
int jiffies;
struct net_local {
    struct enet_statistics stats;
	long open_time;
} lps, *lp = &lps;

unsigned char fake_packet[100];

static int read_eeprom(int ioaddr, int location);
int at1700_probe1(struct device *dev, short ioaddr);
int at1700_report(int ioaddr);
static int net_open(struct device *dev);
static int net_close(struct device *dev);


int
main(int argc, char **argv)
{
#ifdef final_version
    int port_base = 0x300, irq = -1;
#else
    int port_base = 0x320, irq = -1;
#endif
    int errflag = 0, shared_mode = 0;
    int write_eeprom = 0, interface = -1, all_regs = 0, dump = 0;
	int examine = -1, reset_asic = 0;
    int c, longind;
    extern char *optarg;
	short ioaddr;
	int i;

    while ((c = getopt_long(argc, argv, "ae:f:i:p:rsdvw", longopts, &longind))
	   != -1)
	switch (c) {
	case 'e':
	    examine = atoi(optarg);
	    break;
	case 'f':
	    interface = atoi(optarg);
	    break;
	case 'i':
	    irq = atoi(optarg);
	    break;
	case 'p':
	    port_base = strtol(optarg, NULL, 16);
	    break;
	case 'd': dump++; break;
	case 'r': reset_asic++; break;
	case 's': shared_mode++; break;
	case 'v': verbose++;		 break;
	case 'w': write_eeprom++;	 break;
	case 'a': all_regs++;		 break;
	case '?':
	    errflag++;
	}
    if (errflag) {
		fprintf(stderr, "usage:");
		return 2;
    }

    if (verbose)
		printf(version);

    dev->name = "AT1700";
	dev->base_addr = ioaddr = port_base;
    dev->priv = lp;
    
    if (iopl(3) < 0) {
		perror("ethertest: ioperm()");
		return 1;
    }

	if (all_regs) {
		printk("Device registers at %#x:", ioaddr);
		for (i = 0; i < 8; i+=2)
			printk(" %04x", inw(ioaddr + i));
	}
	printk(".\n);

	if (at1700_probe1(dev, ioaddr)) {
		printk("AT1700 not found.\n");
		return 0;
	}

	at1700_report(ioaddr);

	if (all_regs) {
		int i;
		for (i = 0; i < 16; i+=4) {
			printk("EEPROM location %d is %04x %04x %04x %04x.\n", i,
				   read_eeprom(ioaddr, i), read_eeprom(ioaddr, i+1),
				   read_eeprom(ioaddr, i+2), read_eeprom(ioaddr, i+3));
		}
	}
	if (verbose > 1) {
		net_open(dev);
		net_close(dev);
	}

	return 0;
}

int at1700_probe1(struct device *dev, short ioaddr)
{
	unsigned short signature[4]         = {0xffff, 0xffff, 0x7ff7, 0xffff};
	unsigned short signature_invalid[4] = {0xffff, 0xffff, 0x7ff7, 0xdfff};
	char irqmap[4] = {3, 4, 5, 9};
	unsigned short *station_address = (unsigned short *)dev->dev_addr;
	unsigned int i;

	/* Resetting the chip doesn't reset the ISA interface, so don't bother.
	   That means we have to be careful with the register values we probe for.
	   */
	for (i = 0; i < 4; i++)
		if ((inw(ioaddr + 2*i) | signature_invalid[i]) != signature[i]) {
			if (net_debug > 2)
				printk("AT1700 signature match failed at %d (%04x vs. %04x)\n",
					   i, inw(ioaddr + 2*i), signature[i]);
			return -ENODEV;
		}

#ifdef HAVE_PORTRESERVE
	/* Grab the region so we can find another board if autoIRQ fails. */
	snarf_region(ioaddr, 32);
#endif

	dev->base_addr = ioaddr;
	dev->irq = irqmap[ read_eeprom(ioaddr, 0) >> 14 ];

	printk("%s: AT1700 found at %#3x, IRQ %d, address ", dev->name, ioaddr, dev->irq);

	irq2dev_map[dev->irq] = dev;

	for(i = 0; i < 3; i++) {
		unsigned short eeprom_val = read_eeprom(ioaddr, 4+i);
		printk("%04x", eeprom_val);
		station_address[i] = ntohs(eeprom_val);
	}

	dev->if_port = read_eeprom(ioaddr, 12) & 0x4000 ? 0 : 1;
	printk(" using %s.\n", dev->if_port == 0 ? "10baseT" : "150ohm STP");

	/* Set the station address in bank zero. */
	for (i = 0; i < 6; i++)
		outb(dev->dev_addr[i], ioaddr + 8 + i);

	/* Switch to bank 2 and set the multicast table to accept all. */
	outb(0xe4, ioaddr + 7);
	for (i = 0; i < 8; i++)
		outb(0xff, ioaddr + 8 + i);

	/* Set the configuration register 0 to 32K 100ns. byte-wide memory, 16 bit
	   bus access, two 4K Tx queues, and disabled Tx and Rx. */
	outb(0xda, ioaddr + 6);

	/* Power-down the chip.  Aren't we green! */
	outb(0x00, ioaddr + 7);

	return 0;
}

/* Initialize the AT1700 at IOADDR. */
int at1700_report(int ioaddr)
{
	int i;

	/* Switch to bank zero to read the station address. */
	printk("Station address as read is:");
	outb(0xe0, ioaddr + 7);
	for (i = 0; i < 8; i++)
		printk(" %02x", inb(ioaddr + 8 + i));

	printk("\nMulticast filter registers as read is:");
	outb(0xe4, ioaddr + 7);
	for (i = 0; i < 8; i++)
		printk(" %02x", inb(ioaddr + 8 + i));
	printk("\n");
	return 0;
}

static int read_eeprom(int ioaddr, int location)
{
	int i;
	unsigned short retval = 0;
	short ee_addr = ioaddr + EEPROM_Ctrl;
	short ee_daddr = ioaddr + EEPROM_Data;
	int read_cmd = location | EE_READ_CMD;
	short ctrl_val = EE_CS;
	
	if (verbose > 2) printk(" EEPROM reg @ %x (%2x) ", ee_addr, ctrl_val);
	
	outb(ctrl_val, ee_addr);
	
	/* Shift the read command bits out. */
	for (i = 9; i >= 0; i--) {
		short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
		outb(dataval, ee_daddr);
		outb(EE_CS | EE_SHIFT_CLK, ee_addr);	/* EEPROM clock tick. */
		eeprom_delay();
		outb(EE_CS, ee_addr);	/* Finish EEPROM a clock tick. */
		eeprom_delay();
	}
	if (verbose > 3) printf(" ");
	outb(EE_CS, ee_addr);
	
	for (i = 16; i > 0; i--) {
		outb(EE_CS | EE_SHIFT_CLK, ee_addr);
		eeprom_delay();
		retval = (retval << 1) | ((inb(ee_daddr) & EE_DATA_READ) ? 1 : 0);
		outb(EE_CS, 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;
}

static int net_open(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	int ioaddr = dev->base_addr;
	int i;

	/* Powerup the chip, initialize config register 1, and select bank 0. */
	outb(0xe0, ioaddr + 7);

	/* Set the station address in bank zero. */
	for (i = 0; i < 6; i++)
		outb(dev->dev_addr[i], ioaddr + 8 + i);

	/* Switch to bank 2 and set the multicast table to accept all. */
	outb(0xe4, ioaddr + 7);
	for (i = 0; i < 8; i++)
		outb(0xff, ioaddr + 8 + i);

	/* Set the configuration register 0 to 32K 100ns. byte-wide memory, 16 bit
	   bus access, and two 4K Tx queues. */
	outb(0xda, ioaddr + 6);

	/* Same config 0, except enable the Rx and Tx. */
	outb(0x5a, ioaddr + 6);

	lp->open_time = jiffies;

	dev->tbusy = 0;
	dev->interrupt = 0;
	dev->start = 1;

	return 0;
}

/* The inverse routine to net_open(). */
static int net_close(struct device *dev)
{
	struct net_local *lp = (struct net_local *)dev->priv;
	int ioaddr = dev->base_addr;

	lp->open_time = 0;

	dev->tbusy = 1;
	dev->start = 0;

	/* Set configuration register 0 to disable Tx and Rx. */
	outb(0xda, ioaddr + 6);

	/* Power-down the chip.  Green, green, green! */
	outb(0x00, ioaddr + 7);

	/* Update the statistics here. */
	return 0;
}

int
rx_packet(short ioaddr)
{
	return 0;
}

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