#include <asm/bitops.h>
#include "/usr/src/linux/net/inet/dev.h"
#include "pocket.h"

#ifndef __KERNEL__
#define printk printf
#endif

struct net_local lps = {0, 0, 0, 0, 0, 0, 0}, *lp = &lps;

#define pac_cnt_in_tx_buf tx_queue
#define tx_unit_busy	txing
#define saved_tx_size	other_len

static void hardware_send_packet(short ioaddr, void *packet, int length);
static void start_send(short ioaddr, void *packet, int length);
static void write_packet(short ioaddr, int length, unsigned char *packet);
static void terminate(short ioaddr);
static void reset_interface(struct device *dev);
void copy_nodeID(struct device *dev);
unsigned short ee_read_word(short ioaddr);
static void ee_write_bits(short ioaddr, char num_bits, int output);
static void next_bit_out(short ioaddr, char num_bits, short output);

static void
hardware_send_packet(short ioaddr, void *packet, int length)
{
    lps.len = length;
    if (lp->pac_cnt_in_tx_buf == 2)
	return;		/* Full, don't send to device. */
    /* Disable interrupts by writing 0x00 to the Interrupt Mask Register. */
    write_nibble(ioaddr, IMR, 0);
    write_nibble(ioaddr, IMR_h, HNib);
    /* Some LPT ports (e.g. on the PS/2) need this to clear IRQ. */
    inb(ioaddr + STATUS);
    start_send(ioaddr, packet, length);
    /* Re-enable the LPT interrupts. */
    write_nibble(ioaddr, IMR, ISR_RxOK | ISR_TxErr | ISR_TxOK);
    write_nibble(ioaddr, IMR_h, ISRh_RxErr);
    return;
}

static void start_send(short ioaddr, void *packet, int length)
{
    if (lp->pac_cnt_in_tx_buf == 0) {
	/* The Tx buffer is entry. */
	write_packet(ioaddr, length, packet);
    }
    if (lp->tx_unit_busy == 0) {
	write_nibble(ioaddr, TBCR1, lp->saved_tx_size >> 8);
	write_byte(ioaddr, TBCR0, lp->saved_tx_size & 0xff);
	write_nibble(ioaddr, CMR1, CMR1_Xmit);
	lp->tx_unit_busy = 1;
    }
    return;
}

static void write_packet(short ioaddr, int length, unsigned char *packet)
{
    lp->saved_tx_size = length;
    (length + 1) >> 1 ;		/* Round up to word length. */
    ioaddr + DATA;
    outb(EOC+MAR, ioaddr + DATA);
    if (lp->data_mode == 0) {
	unsigned char *p = packet;
	/* Write the packet out, starting with the write addr. */
	outb(WrAddr+MAR, ioaddr + DATA);
	do {
	    write_byte_to_DRAM(ioaddr, *++p);
	} while (--length > 0) ;
    } else {
	/* Write the packet out in slow mode. */
	unsigned char *p = packet;
	unsigned char outbyte = *p;
	outb(Ctrl_LNibWrite, ioaddr + CONTROL);
	outb(WrAddr+MAR, ioaddr + DATA);
	outb((outbyte & 0x0f)|0x40, ioaddr + DATA);
	outb(outbyte & 0x0f, ioaddr + DATA);
	outbyte >>= 4;
	outb(outbyte & 0x0f, ioaddr + DATA);
	outb(Ctrl_HNibWrite, ioaddr + CONTROL);
	while (--length > 0)
	    write_byte_to_DRAMA(ioaddr, *++p);
    }
    /* Terminate the Tx frame.  FinEnd of write: ECB. */
    outb(0xff, ioaddr + DATA);
    outb(Ctrl_HNibWrite|Ctrl_SelData, ioaddr + CONTROL);
    lp->pac_cnt_in_tx_buf++;
}

void
set_address(struct device *dev)
{
    short ioaddr = dev->base_addr;
    int i;
    for (i = 0; i < 6; i++)
	write_byte(ioaddr, IDR0, dev->dev_addr[i]);
}

static void terminate(short ioaddr)
{
    write_nibble(ioaddr, CMR1_h, CMR1h_RESET);
    /* We should sleep for 2048 ticks. */
}

static void reset_interface(struct device *dev)
{
    short ioaddr = dev->base_addr;
    unsigned char tmp;

/*    copy_nodeID(dev);*/
    do {
	terminate(ioaddr);
	set_address(dev);
	write_nibble(ioaddr, CMR2_h, lp->addr_mode);
	tmp = (read_nibble(ioaddr, CMR2_h) >> 3) & 0x0f;
	read_end(ioaddr, CMR2_h);
    } while (tmp != lp->addr_mode);
    write_nibble(ioaddr, CMR2, CMR2_IRQOUT);
    write_nibble(ioaddr, CMR1_h, CMR1h_RxENABLE | CMR1h_TxENABLE);
    write_nibble(ioaddr, IMR, ISR_RxOK | ISR_TxErr | ISR_TxOK);
    write_nibble(ioaddr, IMR_h, ISRh_RxErr);
    lp->fixoverflow = 0;
    lp->txing = 0;
}
#ifdef notyet
static void poll_ISR(short ioaddr)
{
    int status;
    int i;

    while (status = read_nibble(ioaddr, ISR)) {
	read_end(ioaddr, ISR);
	if (status & (ISR_RxOK<<3)) {
	    int read_status;
	    /* Clear the Rx interrupt. */
	    write_nibble(ioaddr, ISR, ISR_RxOK);
	    read_status = read_nibble(ioaddr, CMR1);
	    read_end(ioaddr, CMR1);
	    if (read_status & (CMR1_IRQ << 3)) {
		/*lp->stats.rx_overflow++;*/
		write_nibble(ioaddr, CMR2_h, HNib); /* Set to no-accept mode. */
	    } else if ((read_status & (CMR1_BufEnb << 3)) == 0) {
		struct rx_header head;
		char *p = (char *)&head;
		/* Process the received packet. */
		outb(EOC+MAR, ioaddr + DATA);
		if (lp->data_mode <= 3) {
		    outb(Ctrl_LNibRead, ioaddr + CONTROL);
		    outb(RdAddr+MAR+HNib, ioaddr + DATA);
		    if (lp->data_mode <= 1) /* Mode 0 or 1 */
			for (i = 0; i < sizeof(head); i++)
			    *p++ = read_byteA1(ioaddr, MAR);
		    else	/* Mode 2 or 3 */
			for (i = 0; i < sizeof(head); i++)
			    *p++ = read_byteA2(ioaddr, MAR);
		} else if (lp->data_mode <= 5) { /* Mode 4 or 5 */
		    for (i = 0; i < sizeof(head); i++)
			*p++ = read_byte(ioaddr, MAR);
		} else {	/* Mode 6 or 7 */
		    for (i = 0; i < sizeof(head); i++)
			*p++ = read_byte1(ioaddr, MAR);
		}
		read_end(ioaddr, MAR+HNib);
		outb(Ctrl_SelData, ioaddr + CONTROL);
		if ((head.rx_status & 0x77) == 0x01) {
		    head.rx_count -= 4;	/* Omit FCS (CRC). */
		} else {
		    init_eplc();
		    continue;
		}
	    } else
		continue;
	} else if (status & ((ISR_TxErr + ISR_TxOK)<<3)) {
	    /* Clear the Tx interrupt.  We should check for too many failures
	       and reinitialize the adaptor. */
	    write_nibble(ioaddr, ISR, ISR_TxErr + ISR_TxOK);
	    if (status & (ISR_TxErr<<3)) {
		/* Attempt to retransmit. */
		write_nibble(ioaddr, CMR1, CMR1_ReXmit + CMR1_Xmit);
		continue;
	    }
	    /* Finish up the transmit. */
	    dev->tbusy = 0;
	    lp->txing = 0;
	    if (--lp->tx_queue)
		go_tx();
	    if (other_len == 0)
		continue;
	    start_send(send_head);
	    send_dequeue();
	    continue;
	}
    }
    read_end(ioaddr, ISR);
}
#endif


/* Read the station address PROM.  This might be either a word-wide EEPROM
   or a 74S288.
   An EEPROM read command is 0x60 + offset.  See the other ethercard drivers
   for details. */
void get_node_ID(struct device *dev)
{
    short ioaddr = dev->base_addr;
    unsigned short station_address[64];
    int i;

    printk("Running 'get_node_ID(%s:%#x)': ", dev->name, ioaddr); 
    write_nibble(ioaddr, CMR2, CMR2_PAGE);    /* Point to page 1. */
    for (i = 0; i < 3; i++) {
	unsigned short station_address;
	ee_write_bits(ioaddr, EE_CMD_SIZE, EE_READ_CMD + i + 15);
	station_address = ee_read_word(ioaddr);
	printk("%04x", station_address);
	((unsigned short *)dev->dev_addr)[i] = ntohs(station_address);
	write_nibble(ioaddr, PCMR_h, HNib + EE_SHIFT_CLK);
	eeprom_delay(5);
    }
    write_nibble(ioaddr, CMR2, CMR2_NULL);
#ifdef notdef
    printk(" station address is ");
    for (i = 0; i < 6; i++)
	printk(" %02x", dev->dev_addr[i]);
#endif
    printk(".\n");
}

unsigned short
ee_read_word(short ioaddr)
{
    unsigned eedata;
    int i;

    if (verbose > 4)  printk("Reading from EEPROM: ");
    for (i = 0; i < 17; i++) {
	int inval;
	write_nibble(ioaddr, PCMR_h, EE_CLK_LOW);
	slowly(5);				/* Wait 1us. */
	write_nibble(ioaddr, PCMR_h, EE_CLK_HIGH);	/* Set the shift clock high. */
	eedata <<= 1;
	inval = read_nibble(ioaddr, PDR);
	if (verbose > 4)  printk(" %02x", inval);
	if (inval & 0x08)
	    eedata++;
	read_end(ioaddr, PDR);
	slowly(2);
    }
    if (verbose > 4)  printk(".\n");
    return eedata;
}

/*
 *	   ________________
 * CS : __|
 *	       ___     ___
 * SK : ______|   |___|   |
 *	  _______ _______
 * DI :	 X_______X_______X
 */

static void
ee_write_bits(short ioaddr, char num_bits, int output)
{
    if (verbose > 6)
	printk("EEPROM write %d bits of %x: Outputting value ",
	       num_bits, output);
    while (--num_bits >= 0) {
	char outval = test_bit(num_bits, &output) ? 1 : 0;
	if (verbose > 6)  printk("%d", outval);
	write_nibble(ioaddr, PCMR_h, outval | EE_CLK_LOW);
	eeprom_delay(5);
	write_nibble(ioaddr, PCMR_h, outval | EE_CLK_HIGH);
	eeprom_delay(5);
    }
    if (verbose > 6) printk(".\n");
}

void get_node_ID_1(struct device *dev)
{
    short ioaddr = dev->base_addr;
    unsigned char station_address[6];
    int i;

    printk("Running 'get_node_ID_1(%s:%#x)'...\n", dev->name, ioaddr); 
    write_nibble(ioaddr, CMR2, CMR2_PAGE);    /* Point to page 1. */
    for (i = 0; i < 6; i++) {
	unsigned int inval, result;
	write_nibble(ioaddr, PCMR, i);
	write_nibble(ioaddr, PCMR_h, 0x04 | HNib);
	write_nibble(ioaddr, PCMR_h, HNib);
	inval = read_nibble(ioaddr, PDR);
	printk("Read in nibble %02x.\n", inval);
	result = (inval >> 3);
	inval = read_nibble(ioaddr, PDR+HNib);
	printk("Read in nibble %02x.\n", inval);
	result |= (inval << 1);
	station_address[i] = result;
	read_end(ioaddr, PDR + HNib);
    }
    write_nibble(ioaddr, CMR2, CMR2_NULL);
}

int
atp_probe(short ioaddr)
{
    int saved_ctrl_reg, status;

    outb(0xff, ioaddr + DATA);
    /* Save the original value of the Control register, in case we guessed
       wrong. */
    saved_ctrl_reg = inb(ioaddr + CONTROL);
    /* IRQEN=0, SLCTB=high INITB=high, AUTOFDB=high, STBB=high. */
    outb(0x04, ioaddr + CONTROL);
    write_nibble(ioaddr, CMR1_h, CMR1h_RESET);
    eeprom_delay(2048);
    status = read_nibble(ioaddr, CMR1);
    printk("Probe read was: %02x.\n", status);
    read_end(ioaddr, CMR1);

    if ((status & 0x78) != 0x08) {
	printk("Pocket adaptor probe failed at %#x.\n", ioaddr);
	outb(saved_ctrl_reg, ioaddr + CONTROL);
	return 1;
    }
    printk("Pocket adaptor at %#x passed probe step 1, status %02x.\n",
	   ioaddr, status);
    status = read_nibble(ioaddr, CMR2_h);
    read_end(ioaddr, CMR2_h);
    if ((status & 0x78) == 0x10) {
	printk("Pocket adaptor probe failed at %#x with status %02x.\n",
	       ioaddr, status);
	outb(saved_ctrl_reg, ioaddr + CONTROL);
	return 1;
    }
    printk("Pocket adaptor at %#x passed probe step 2, status %02x.\n",
	   ioaddr, status);
    /* Find the IRQ used. */
    write_byte(ioaddr, CMR2, 0x01); 		/* No accept mode, IRQ out. */
    write_nibble(ioaddr, CMR1_h, HNib+3);	/* Enable Tx and Rx. */
    /* Omit autoIRQ routine. */
    write_nibble(ioaddr, CMR1_h, HNib);
    write_nibble(ioaddr, CMR2, CMR2_NULL);
#ifdef notdef
    /* RAM test to select the proper data I/O mode. */
    for (i = 0; i < 7; i++)
	ram_test(ioaddr, i);
#endif

    return 0;
}

/*
 * Local variables:
 *  compile-command: "gcc -O -c pocket.c"
 * End:
 */
