/*
**  File:	3c509.c		Jun. 01, 2000
**
**  Author:	Giovanni Falzoni <gfalzoni@inwind.it>
**
**  This file contains specific implementation of the ethernet
**  device driver for 3Com Etherlink III (3c509) boards.
**  NOTE: The board has to be setup to disable PnP and to assign
**	  I/O base and IRQ.  The driver is for ISA bus only
**
**  $Id: 3c509.c,v 1.3 2005/08/05 19:08:43 beng Exp $
*/

#include "drivers.h"
#include <minix/com.h>
#include <net/hton.h>
#include <net/gen/ether.h>
#include <net/gen/eth_io.h>

#include "dp.h"

#if (ENABLE_3C509 == 1)

#include "3c509.h"

static const char *const IfNamesMsg[] = {
	"10BaseT", "AUI", "unknown", "BNC",
};

/*
**  Name:	void el3_update_stats(dpeth_t *dep)
**  Function:	Reads statistic counters from board
**  		and updates local counters.
*/
static void el3_update_stats(dpeth_t * dep)
{

  /* Disables statistics while reading and switches to the correct window */
  outw_el3(dep, REG_CmdStatus, CMD_StatsDisable);
  SetWindow(WNO_Statistics);

  /* Reads everything, adding values to the local counters */
  dep->de_stat.ets_sendErr += inb_el3(dep, REG_TxCarrierLost);	/* Reg. 00 */
  dep->de_stat.ets_sendErr += inb_el3(dep, REG_TxNoCD);		/* Reg. 01 */
  dep->de_stat.ets_collision += inb_el3(dep, REG_TxMultColl);	/* Reg. 02 */
  dep->de_stat.ets_collision += inb_el3(dep, REG_TxSingleColl);	/* Reg. 03 */
  dep->de_stat.ets_collision += inb_el3(dep, REG_TxLate);	/* Reg. 04 */
  dep->de_stat.ets_recvErr += inb_el3(dep, REG_RxDiscarded);	/* Reg. 05 */
  dep->de_stat.ets_packetT += inb_el3(dep, REG_TxFrames);	/* Reg. 06 */
  dep->de_stat.ets_packetR += inb_el3(dep, REG_RxFrames);	/* Reg. 07 */
  dep->de_stat.ets_transDef += inb_el3(dep, REG_TxDefer);	/* Reg. 08 */
  dep->bytes_Rx += (unsigned) inw_el3(dep, REG_RxBytes);	/* Reg. 10 */
  dep->bytes_Tx += (unsigned) inw_el3(dep, REG_TxBytes);	/* Reg. 12 */

  /* Goes back to operating window and enables statistics */
  SetWindow(WNO_Operating);
  outw_el3(dep, REG_CmdStatus, CMD_StatsEnable);

  return;
}

/*
**  Name:	void el3_getstats(dpeth_t *dep)
**  Function:	Reads statistics counters from board.
*/
static void el3_getstats(dpeth_t * dep)
{

  lock();
  el3_update_stats(dep);
  unlock();
  return;
}

/*
**  Name:	void el3_dodump(dpeth_t *dep)
**  Function:	Dumps counter on screen (support for console display).
*/
static void el3_dodump(dpeth_t * dep)
{

  el3_getstats(dep);
  return;
}

/*
**  Name:	void el3_rx_mode(dpeth_t *dep)
**  Function:	Initializes receiver mode
*/
static void el3_rx_mode(dpeth_t * dep)
{

  dep->de_recv_mode = FilterIndividual;
  if (dep->de_flags & DEF_BROAD) dep->de_recv_mode |= FilterBroadcast;
  if (dep->de_flags & DEF_MULTI) dep->de_recv_mode |= FilterMulticast;
  if (dep->de_flags & DEF_PROMISC) dep->de_recv_mode |= FilterPromiscuous;

  outw_el3(dep, REG_CmdStatus, CMD_RxReset);
  outw_el3(dep, REG_CmdStatus, CMD_SetRxFilter | dep->de_recv_mode);
  outw_el3(dep, REG_CmdStatus, CMD_RxEnable);

  return;
}

/*
**  Name:	void el3_reset(dpeth_t *dep)
**  Function:	Reset function specific for Etherlink hardware.
*/
static void el3_reset(dpeth_t * dep)
{

  return;			/* Done */
}

/*
**  Name:	void el3_write_fifo(dpeth_t * dep, int pktsize);
**  Function:	Writes a packet from user area to board.
**  Remark:	Writing a word/dword at a time may result faster
**  		but is a lot more complicated. Let's go simpler way.
*/
static void el3_write_fifo(dpeth_t * dep, int pktsize)
{
  phys_bytes phys_user;
  int bytes, ix = 0;
  iovec_dat_t *iovp = &dep->de_write_iovec;
  int padding = pktsize;

  do {				/* Writes chuncks of packet from user buffers */

	bytes = iovp->iod_iovec[ix].iov_size;	/* Size of buffer */
	if (bytes > pktsize) bytes = pktsize;
	/* Writes from user buffer to Tx FIFO */
        outsb(dep->de_data_port, iovp->iod_proc_nr,
              (void*)(iovp->iod_iovec[ix].iov_addr), bytes);
	if (++ix >= IOVEC_NR) {	/* Next buffer of IO vector */
		dp_next_iovec(iovp);
		ix = 0;
	}
	/* Till packet done */
  } while ((pktsize -= bytes) > 0);
  while ((padding++ % sizeof(long)) != 0) outb(dep->de_data_port, 0x00);
  return;
}

/*
**  Name:	void el3_recv(dpeth_t *dep, int fromint, int size)
**  Function:	Receive function.  Called from interrupt handler or
**  		from main to unload recv. buffer (packet to client)
*/
static void el3_recv(dpeth_t *dep, int fromint, int size)
{
  buff_t *rxptr;

  while ((dep->de_flags & DEF_READING) && (rxptr = dep->de_recvq_head)) {

	lock();			/* Remove buffer from queue */
	if (dep->de_recvq_tail == dep->de_recvq_head)
		dep->de_recvq_head = dep->de_recvq_tail = NULL;
	else
		dep->de_recvq_head = rxptr->next;
	unlock();

	/* Copy buffer to user area and free it */
	mem2user(dep, rxptr);

	dep->de_read_s = rxptr->size;
	dep->de_flags |= DEF_ACK_RECV;
	dep->de_flags &= NOT(DEF_READING);

	/* Return buffer to the idle pool */
	free_buff(dep, rxptr);
  }
  return;
}

/*
**  Name:	void el3_rx_complete(dpeth_t * dep);
**  Function:	Upon receiving a packet, provides status checks
**  		and if packet is OK copies it to local buffer.
*/
static void el3_rx_complete(dpeth_t * dep)
{
  short int RxStatus;
  int pktsize;
  buff_t *rxptr;

  RxStatus = inw_el3(dep, REG_RxStatus);
  pktsize = RxStatus & RXS_Length;	/* Mask off packet length */

  if (RxStatus & RXS_Error) {

	/* First checks for receiving errors */
	RxStatus &= RXS_ErrType;
	switch (RxStatus) {	/* Bad packet (see error type) */
	    case RXS_Dribble:
	    case RXS_Oversize:
	    case RXS_Runt:	dep->de_stat.ets_recvErr += 1;	break;
	    case RXS_Overrun:	dep->de_stat.ets_OVW += 1;	break;
	    case RXS_Framing:	dep->de_stat.ets_frameAll += 1;	break;
	    case RXS_CRC:	dep->de_stat.ets_CRCerr += 1;	break;
	}

  } else if ((rxptr = alloc_buff(dep, pktsize + sizeof(buff_t))) == NULL) {
	/* Memory not available. Drop packet */
	dep->de_stat.ets_fifoOver += 1;

  } else {
	/* Good packet.  Read it from FIFO */
	insb(dep->de_data_port, SELF, rxptr->buffer, pktsize);
	rxptr->next = NULL;
	rxptr->size = pktsize;

	lock();			/* Queue packet to receive queue */
	if (dep->de_recvq_head == NULL)
		dep->de_recvq_head = rxptr;
	else
		dep->de_recvq_tail->next = rxptr;
	dep->de_recvq_tail = rxptr;
	unlock();

	/* Reply to pending Receive requests, if any */
	el3_recv(dep, TRUE, pktsize);
  }

  /* Discard top packet from queue */
  outw_el3(dep, REG_CmdStatus, CMD_RxDiscard);

  return;
}

/*
**  Name:	void el3_send(dpeth_t *dep, int count)
**  Function:	Send function.  Called from main to transit a packet or
**  		from interrupt handler when Tx FIFO gets available.
*/
static void el3_send(dpeth_t * dep, int from_int, int count)
{
  clock_t now;
  int ix;
  short int TxStatus;

  getuptime(&now);
  if ((dep->de_flags & DEF_XMIT_BUSY) &&
      (now - dep->de_xmit_start) > 4) {

	DEBUG(printf("3c509:  Transmitter timed out. Resetting ....\n");)
	dep->de_stat.ets_sendErr += 1;
	/* Resets and restars the transmitter */
	outw_el3(dep, REG_CmdStatus, CMD_TxReset);
	outw_el3(dep, REG_CmdStatus, CMD_TxEnable);
	dep->de_flags &= NOT(DEF_XMIT_BUSY);
  }
  if (!(dep->de_flags & DEF_XMIT_BUSY)) {

	/* Writes Transmitter preamble 1st Word (packet len, no ints) */
	outw_el3(dep, REG_TxFIFO, count);
	/* Writes Transmitter preamble 2nd Word (all zero) */
	outw_el3(dep, REG_TxFIFO, 0);
	/* Writes packet */
	el3_write_fifo(dep, count);

	getuptime(&dep->de_xmit_start);
	dep->de_flags |= (DEF_XMIT_BUSY | DEF_ACK_SEND);
	if (inw_el3(dep, REG_TxFree) > ETH_MAX_PACK_SIZE) {
		/* Tx has enough room for a packet of maximum size */
		dep->de_flags &= NOT(DEF_XMIT_BUSY | DEF_SENDING);
	} else {
		/* Interrupt driver when enough room is available */
		outw_el3(dep, REG_CmdStatus, CMD_SetTxAvailable | ETH_MAX_PACK_SIZE);
		dep->de_flags &= NOT(DEF_SENDING);
	}

	/* Pops Tx status stack */
	for (ix = 4; --ix && (TxStatus = inb_el3(dep, REG_TxStatus)) > 0;) {
		if (TxStatus & 0x38) dep->de_stat.ets_sendErr += 1;
		if (TxStatus & 0x30)
			outw_el3(dep, REG_CmdStatus, CMD_TxReset);
		if (TxStatus & 0x3C)
			outw_el3(dep, REG_CmdStatus, CMD_TxEnable);
		outb_el3(dep, REG_TxStatus, 0);
	}
  }
  return;
}

/*
**  Name:	void el3_close(dpeth_t *dep)
**  Function:	Stops board and makes it ready to shut down.
*/
static void el3_close(dpeth_t * dep)
{

  /* Disables statistics, Receiver and Transmitter */
  outw_el3(dep, REG_CmdStatus, CMD_StatsDisable);
  outw_el3(dep, REG_CmdStatus, CMD_RxDisable);
  outw_el3(dep, REG_CmdStatus, CMD_TxDisable);

  if (dep->de_if_port == BNC_XCVR) {
	outw_el3(dep, REG_CmdStatus, CMD_StopIntXcvr);
	/* milli_delay(5); */

  } else if (dep->de_if_port == TP_XCVR) {
	SetWindow(WNO_Diagnostics);
	outw_el3(dep, REG_MediaStatus, inw_el3(dep, REG_MediaStatus) &
		 NOT((MediaLBeatEnable | MediaJabberEnable)));
	/* milli_delay(5); */
  }
  DEBUG(printf("%s: stopping Etherlink ... \n", dep->de_name));
  /* Issues a global reset
  outw_el3(dep, REG_CmdStatus, CMD_GlobalReset); */
  sys_irqdisable(&dep->de_hook);	/* Disable interrupt */
 
  return;
}

/*
**  Name:	void el3_interrupt(dpeth_t *dep)
**  Function:	Interrupt handler.  Acknwledges transmit interrupts
**  		or unloads receive buffer to memory queue.
*/
static void el3_interrupt(dpeth_t * dep)
{
  int loop;
  unsigned short isr;

  for (loop = 5; loop > 0 && ((isr = inw_el3(dep, REG_CmdStatus)) &
         (INT_Latch | INT_RxComplete | INT_UpdateStats)); loop -= 1) {

	if (isr & INT_RxComplete)	/* Got a new packet */
		el3_rx_complete(dep);

	if (isr & INT_TxAvailable) {	/* Tx has room for big packets */
		DEBUG(printf("3c509: got Tx interrupt, Status=0x%04x\n", isr);)
		dep->de_flags &= NOT(DEF_XMIT_BUSY);
		outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | INT_TxAvailable);
		if (dep->de_flags & DEF_SENDING)	/* Send pending */
			el3_send(dep, TRUE, dep->de_send_s);
	}
	if (isr & (INT_AdapterFail | INT_RxEarly | INT_UpdateStats)) {

		if (isr & INT_UpdateStats)	/* Empties statistics */
			el3_getstats(dep);

		if (isr & INT_RxEarly)	/* Not really used. Do nothing */
			outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | (INT_RxEarly));

		if (isr & INT_AdapterFail) {
			/* Adapter error. Reset and re-enable receiver */
			DEBUG(printf("3c509: got Rx fail interrupt, Status=0x%04x\n", isr);)
			el3_rx_mode(dep);
			outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | INT_AdapterFail);
		}
	}

	/* Acknowledge interrupt */
	outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | (INT_Latch | INT_Requested));
  }
  return;
}

/*
**  Name:	unsigned el3_read_eeprom(port_t port, unsigned address);
**  Function:	Reads the EEPROM at specified address
*/
static unsigned el3_read_eeprom(port_t port, unsigned address)
{
  unsigned int result;
  int bit;

  address |= EL3_READ_EEPROM;
  outb(port, address);
  milli_delay(5);		/* Allows EEPROM reads */
  for (result = 0, bit = 16; bit > 0; bit -= 1) {
	result = (result << 1) | (inb(port) & 0x0001);
  }
  return result;
}

/*
**  Name:	void el3_read_StationAddress(dpeth_t *dep)
**  Function:	Reads station address from board
*/
static void el3_read_StationAddress(dpeth_t * dep)
{
  unsigned int ix, rc;

  for (ix = EE_3COM_NODE_ADDR; ix < SA_ADDR_LEN;) {
	/* Accesses with word No. */
	rc = el3_read_eeprom(dep->de_id_port, ix / 2);
	/* Swaps bytes of word */
	dep->de_address.ea_addr[ix++] = (rc >> 8) & 0xFF;
	dep->de_address.ea_addr[ix++] = rc & 0xFF;
  }
  return;
}

/*
**  Name:	void el3_open(dpeth_t *dep)
**  Function:	Initalizes board hardware and driver data structures.
*/
static void el3_open(dpeth_t * dep)
{
  unsigned int AddrCfgReg, ResCfgReg;
  unsigned int ix;

  el3_read_StationAddress(dep);	/* Get ethernet address */

  /* Get address and resource configurations */
  AddrCfgReg = el3_read_eeprom(dep->de_id_port, EE_ADDR_CFG);
  ResCfgReg = el3_read_eeprom(dep->de_id_port, EE_RESOURCE_CFG);
  outb(dep->de_id_port, EL3_ACTIVATE);	/* Activate the board */

  /* Gets xcvr configuration */
  dep->de_if_port = AddrCfgReg & EL3_CONFIG_XCVR_MASK;

  AddrCfgReg = ((AddrCfgReg & EL3_CONFIG_IOBASE_MASK) << 4) + EL3_IO_BASE_ADDR;
  if (AddrCfgReg != dep->de_base_port)
	panic(dep->de_name, "Bad I/O port for Etherlink board", NO_NUM);

  ResCfgReg >>= 12;
  dep->de_irq &= NOT(DEI_DEFAULT);	/* Strips the default flag */
  if (ResCfgReg != dep->de_irq) panic(dep->de_name, "Bad IRQ for Etherlink board", NO_NUM);

  SetWindow(WNO_Setup);

  /* Reset transmitter and receiver */
  outw_el3(dep, REG_CmdStatus, CMD_TxReset);
  outw_el3(dep, REG_CmdStatus, CMD_RxReset);

  /* Enable the adapter */
  outb_el3(dep, REG_CfgControl, EL3_EnableAdapter);
  /* Disable Status bits */
  outw_el3(dep, REG_CmdStatus, CMD_SetStatusEnab + 0x00);

  /* Set "my own" address */
  SetWindow(WNO_StationAddress);
  for (ix = 0; ix < 6; ix += 1)
	outb_el3(dep, REG_SA0_1 + ix, dep->de_address.ea_addr[ix]);

  /* Start Transceivers as required */
  if (dep->de_if_port == BNC_XCVR) {
	/* Start internal transceiver for Coaxial cable */
	outw_el3(dep, REG_CmdStatus, CMD_StartIntXcvr);
	milli_delay(5);

  } else if (dep->de_if_port == TP_XCVR) {
	/* Start internal transceiver for Twisted pair cable */
	SetWindow(WNO_Diagnostics);
	outw_el3(dep, REG_MediaStatus,
		 inw_el3(dep, REG_MediaStatus) | (MediaLBeatEnable | MediaJabberEnable));
  }

  /* Switch to the statistic window, and clear counts (by reading) */
  SetWindow(WNO_Statistics);
  for (ix = REG_TxCarrierLost; ix <= REG_TxDefer; ix += 1) inb_el3(dep, ix);
  inw_el3(dep, REG_RxBytes);
  inw_el3(dep, REG_TxBytes);

  /* Switch to operating window for normal use */
  SetWindow(WNO_Operating);

  /* Receive individual address & broadcast. (Mofified later by rx_mode) */
  outw_el3(dep, REG_CmdStatus, CMD_SetRxFilter |
	 (FilterIndividual | FilterBroadcast));

  /* Turn on statistics */
  outw_el3(dep, REG_CmdStatus, CMD_StatsEnable);

  /* Enable transmitter and receiver */
  outw_el3(dep, REG_CmdStatus, CMD_TxEnable);
  outw_el3(dep, REG_CmdStatus, CMD_RxEnable);

  /* Enable all the status bits */
  outw_el3(dep, REG_CmdStatus, CMD_SetStatusEnab | 0xFF);

  /* Acknowledge all interrupts to clear adapter. Enable interrupts */
  outw_el3(dep, REG_CmdStatus, CMD_Acknowledge | 0xFF);
  outw_el3(dep, REG_CmdStatus, CMD_SetIntMask |
    (INT_Latch | INT_TxAvailable | INT_RxComplete | INT_UpdateStats));

  /* Ready to operate, sets the environment for eth_task */
  dep->de_data_port = dep->de_base_port;
  /* Allocates Rx/Tx buffers */
  init_buff(dep, NULL);

  /* Device specific functions */
  dep->de_recvf = el3_recv;
  dep->de_sendf = el3_send;
  dep->de_flagsf = el3_rx_mode;
  dep->de_resetf = el3_reset;
  dep->de_getstatsf = el3_getstats;
  dep->de_dumpstatsf = el3_dodump;
  dep->de_interruptf = el3_interrupt;

  printf("%s: Etherlink III (%s) at %X:%d, %s port - ",
         dep->de_name, "3c509", dep->de_base_port, dep->de_irq,
         IfNamesMsg[dep->de_if_port >> 14]);
  for (ix = 0; ix < SA_ADDR_LEN; ix += 1)
	printf("%02X%c", dep->de_address.ea_addr[ix],
	       ix < SA_ADDR_LEN - 1 ? ':' : '\n');

  return;			/* Done */
}

/*
**  Name:	unsigned int el3_checksum(port_t port);
**  Function:	Reads EEPROM and computes checksum.
*/
static unsigned short el3_checksum(port_t port)
{
  unsigned short rc, checksum, address;
  unsigned char lo, hi;

  for (checksum = address = 0; address < 15; address += 1) {
	rc = el3_read_eeprom(port, address);
	lo = rc & 0xFF;
	hi = (rc >> 8) & 0xFF;
	if ((address == EE_PROD_ID && (rc & EE_PROD_ID_MASK) != EL3_PRODUCT_ID) ||
	    (address == EE_3COM_CODE && rc != EL3_3COM_CODE))
		return address;
	if (address == EE_ADDR_CFG ||
	    address == EE_RESOURCE_CFG ||
	    address == EE_SW_CONFIG_INFO) {
		lo ^= hi;
		hi = 0;
	} else {
		hi ^= lo;
		lo = 0;
	}
	rc = ((unsigned) hi << 8) + lo;
	checksum ^= rc;
  }
  rc = el3_read_eeprom(port, address);
  return(checksum ^= rc);	/* If OK checksum is 0 */
}

/*
**  Name:	void el3_write_id(port_t port);
**  Function:	Writes the ID sequence to the board.
*/
static void el3_write_id(port_t port)
{
  int ix, pattern;

  outb(port, 0);		/* Selects the ID port */
  outb(port, 0);		/* Resets hardware pattern generator */
  for (pattern = ix = 0x00FF; ix > 0; ix -= 1) {
	outb(port, pattern);
	pattern <<= 1;
	pattern = (pattern & 0x0100) ? pattern ^ 0xCF : pattern;
  }
  return;
}

/*
**  Name:	int el3_probe(dpeth_t *dep)
**  Function:	Checks for presence of the board.
*/
PUBLIC int el3_probe(dpeth_t * dep)
{
  port_t id_port;

  /* Don't ask me what is this for !! */
  outb(0x0279, 0x02);	/* Select PnP config control register. */
  outb(0x0A79, 0x02);	/* Return to WaitForKey state. */
  /* Tests I/O ports in the 0x1xF range for a valid ID port */
  for (id_port = 0x110; id_port < 0x200; id_port += 0x10) {
	outb(id_port, 0x00);
	outb(id_port, 0xFF);
	if (inb(id_port) & 0x01) break;
  }
  if (id_port == 0x200) return 0;	/* No board responding */

  el3_write_id(id_port);
  outb(id_port, EL3_ID_GLOBAL_RESET);	/* Reset the board */
  milli_delay(5);		/* Technical reference says 162 micro sec. */
  el3_write_id(id_port);
  outb(id_port, EL3_SET_TAG_REGISTER);
  milli_delay(5);

  dep->de_id_port = id_port;	/* Stores ID port No. */
  dep->de_ramsize =		/* RAM size is meaningless */
	dep->de_offset_page = 0;
  dep->de_linmem = 0L;		/* Access is via I/O port  */

  /* Device specific functions */
  dep->de_initf = el3_open;
  dep->de_stopf = el3_close;

  return(el3_checksum(id_port) == 0);	/* Etherlink board found/not found */
}

#endif				/* ENABLE_3C509 */

/** 3c509.c **/
