/*
 * source in this file came from
 * the Mach ethernet boot written by Leendert van Doorn.
 *
 * A very simple network driver for WD80x3 boards that polls.
 *
 * Copyright (c) 1992 by Leendert van Doorn
 *
 *	$Id: wd80x3.c,v 1.3 1993/08/02 17:53:06 mycroft Exp $
 */

#include "proto.h"
#include "assert.h"
#include "packet.h"
#include "ether.h"
#include "dp8390.h"

/* configurable parameters */
#define	WD_BASEREG	0x280		/* base register */
/* the base address doesn't have to be particularly accurate - the
   board seems to pick up on addresses in the range a0000..effff.
   */
#define	WD_BASEMEM	0xd0000		/* base ram */

/* bit definitions for board features */
#define	INTERFACE_CHIP	01		/* has an WD83C583 interface chip */
#define	BOARD_16BIT	02		/* 16 bit board */
#define	SLOT_16BIT	04		/* 16 bit slot */

/* register offset definitions */
#define	WD_MSR		0x00		/* control (w) and status (r) */
#define WD_REG0		0x00		/* generic register definitions */
#define WD_REG1		0x01
#define WD_REG2		0x02
#define WD_REG3		0x03
#define WD_REG4		0x04
#define WD_REG5		0x05
#define WD_REG6		0x06
#define WD_REG7		0x07
#define	WD_EA0		0x08		/* most significant addr byte */
#define	WD_EA1		0x09
#define	WD_EA2		0x0A
#define	WD_EA3		0x0B
#define	WD_EA4		0x0C
#define	WD_EA5		0x0D		/* least significant addr byte */
#define	WD_LTB		0x0E		/* LAN type byte */
#define	WD_CHKSUM	0x0F		/* sum from WD_EA0 upto here is 0xFF */
#define	WD_DP8390	0x10		/* natsemi chip */

/* bits in control register */
#define	WD_MSR_MEMMASK	0x3F		/* memory enable bits mask */
#define	WD_MSR_MENABLE	0x40		/* memory enable */
#define	WD_MSR_RESET	0x80		/* software reset */

/* bits in bus size register */
#define	WD_BSR_16BIT	0x01		/* 16 bit bus */

/* bits in LA address register */
#define	WD_LAAR_A19	0x01		/* address lines for above 1Mb ram */
#define	WD_LAAR_LAN16E	0x40		/* enables 16bit shrd RAM for LAN */
#define	WD_LAAR_MEM16E	0x80		/* enables 16bit shrd RAM for host */

u_char eth_myaddr[ETH_ADDRSIZE];

static int boardid;
static dpconf_t dpc;

/*
 * Determine whether wd8003 hardware performs register aliasing
 * (i.e. whether it is an old WD8003E board).
 */
static int
Aliasing(void) {
  if (inb(WD_BASEREG + WD_REG1) != inb(WD_BASEREG + WD_EA1))
    return 0;
  if (inb(WD_BASEREG + WD_REG2) != inb(WD_BASEREG + WD_EA2))
    return 0;
  if (inb(WD_BASEREG + WD_REG3) != inb(WD_BASEREG + WD_EA3))
    return 0;
  if (inb(WD_BASEREG + WD_REG4) != inb(WD_BASEREG + WD_EA4))
    return 0;
  if (inb(WD_BASEREG + WD_REG7) != inb(WD_BASEREG + WD_CHKSUM))
    return 0;
  return 1;
}

/*
 * This trick is stolen from the clarkson packet driver
TBD - this is _ugly_ bogus! should use system timer
 */
static void
LongPause(void) {
  short i;
  for (i = 1600; i > 0; i++)
    (void) inb(0x61);
}

/*
 * Determine whether this board has 16-bit capabilities
 */
static int
BoardIs16Bit(void) {
  u_char bsreg = inb(WD_BASEREG + WD_REG1);

  outb(WD_BASEREG + WD_REG1, bsreg ^ WD_BSR_16BIT);
  LongPause();
  if (inb(WD_BASEREG + WD_REG1) == bsreg) {
    /*
     * Pure magic: LTB is 0x05 indicates that this is a WD8013EB board,
     * 0x27 indicates that this is an WD8013 Elite board, and 0x29
     * indicates an SMC Elite 16 board.
     */
    u_char tlb = inb(WD_BASEREG + WD_LTB);
    return tlb == 0x05 || tlb == 0x27 || tlb == 0x29;
  }
  outb(WD_BASEREG + WD_REG1, bsreg);
  return 1;
}

/*
 * Determine whether the 16 bit capable board is plugged
 * into a 16 bit slot.
 */
static int
SlotIs16Bit(void) {
  return inb(WD_BASEREG + WD_REG1) & WD_BSR_16BIT;
}

/*
 * Reset ethernet board after a timeout
 */
void
EtherReset(void) {
  int dpreg = dpc.dc_reg;
  /* initialize the board */
  outb(WD_BASEREG + WD_MSR,
       WD_MSR_MENABLE | (((u_long)WD_BASEMEM >> 13) & WD_MSR_MEMMASK));

  /* reset dp8390 ethernet chip */
  outb(dpreg + DP_CR, CR_STP|CR_DM_ABORT);

  /* initialize first register set */
  outb(dpreg + DP_IMR, 0);
  outb(dpreg + DP_CR, CR_PS_P0|CR_STP|CR_DM_ABORT);
  outb(dpreg + DP_TPSR, dpc.dc_tpsr);
  outb(dpreg + DP_PSTART, dpc.dc_pstart);
  outb(dpreg + DP_PSTOP, dpc.dc_pstop);
  outb(dpreg + DP_BNRY, dpc.dc_pstart);
  outb(dpreg + DP_RCR, RCR_MON);
  outb(dpreg + DP_TCR, TCR_NORMAL|TCR_OFST);
  if (boardid & SLOT_16BIT)
    outb(dpreg + DP_DCR, DCR_WORDWIDE|DCR_8BYTES);
  else
    outb(dpreg + DP_DCR, DCR_BYTEWIDE|DCR_8BYTES);
  outb(dpreg + DP_RBCR0, 0);
  outb(dpreg + DP_RBCR1, 0);
  outb(dpreg + DP_ISR, 0xFF);

  /* initialize second register set */
  outb(dpreg + DP_CR, CR_PS_P1|CR_DM_ABORT);
  outb(dpreg + DP_PAR0, eth_myaddr[0]);
  outb(dpreg + DP_PAR1, eth_myaddr[1]);
  outb(dpreg + DP_PAR2, eth_myaddr[2]);
  outb(dpreg + DP_PAR3, eth_myaddr[3]);
  outb(dpreg + DP_PAR4, eth_myaddr[4]);
  outb(dpreg + DP_PAR5, eth_myaddr[5]);
  outb(dpreg + DP_CURR, dpc.dc_pstart+1);

  /* and back to first register set */
  outb(dpreg + DP_CR, CR_PS_P0|CR_DM_ABORT);
  outb(dpreg + DP_RCR, RCR_AB);

  /* flush counters */
  (void) inb(dpreg + DP_CNTR0);
  (void) inb(dpreg + DP_CNTR1);
  (void) inb(dpreg + DP_CNTR2);

  /* and go ... */
  outb(dpreg + DP_CR, CR_STA|CR_DM_ABORT);
}

/*
 * Initialize the WD80X3 board
 */
int
EtherInit(void) {
  unsigned sum;
  int memsize;
  /* reset the ethernet card */
  outb(WD_BASEREG + WD_MSR, WD_MSR_RESET);
  LongPause();
  outb(WD_BASEREG + WD_MSR, 0);

  /* determine whether the controller is there */
  sum = inb(WD_BASEREG + WD_EA0) + inb(WD_BASEREG + WD_EA1) +
    inb(WD_BASEREG + WD_EA2) + inb(WD_BASEREG + WD_EA3) +
      inb(WD_BASEREG + WD_EA4) + inb(WD_BASEREG + WD_EA5) +
	inb(WD_BASEREG + WD_LTB) + inb(WD_BASEREG + WD_CHKSUM);
  if ((sum & 0xFF) != 0xFF)
    return 0;

  /*
   * Determine the type of board
   */
  boardid = 0;
  if (!Aliasing()) {
    if (BoardIs16Bit()) {
      boardid |= BOARD_16BIT;
      if (SlotIs16Bit())
	boardid |= SLOT_16BIT;
    }
  }
  memsize = (boardid & BOARD_16BIT) ? 0x4000 : 0x2000; /* 16 or 8 Kb */

  /* special setup needed for WD8013 boards */
  if (boardid & SLOT_16BIT)
    outb(WD_BASEREG + WD_REG5, WD_LAAR_A19|WD_LAAR_LAN16E);

  /* get ethernet address */
  eth_myaddr[0] = inb(WD_BASEREG + WD_EA0);
  eth_myaddr[1] = inb(WD_BASEREG + WD_EA1);
  eth_myaddr[2] = inb(WD_BASEREG + WD_EA2);
  eth_myaddr[3] = inb(WD_BASEREG + WD_EA3);
  eth_myaddr[4] = inb(WD_BASEREG + WD_EA4);
  eth_myaddr[5] = inb(WD_BASEREG + WD_EA5);

  /* save settings for future use */
  dpc.dc_reg = WD_BASEREG + WD_DP8390;
  dpc.dc_mem = WD_BASEMEM;
  dpc.dc_tpsr = 0;
  dpc.dc_pstart = 6;
  dpc.dc_pstop = (memsize >> 8) & 0xFF;

  printf("Using wd80x3 board, port 0x%x, iomem 0x%x, iosiz %d\n", WD_BASEREG, WD_BASEMEM, memsize);

  EtherReset();
  return 1;
}

/*
 * Stop ethernet board
 */
void
EtherStop(void) {
  /* stop dp8390, followed by a board reset */
  outb(dpc.dc_reg + DP_CR, CR_STP|CR_DM_ABORT);
  outb(WD_BASEREG + WD_MSR, WD_MSR_RESET);
  outb(WD_BASEREG + WD_MSR, 0);
}

/* TBD - all users must take care to use the current "data seg" value
when moving data from/to the controller */
static void
WdCopy(u_long src, u_long dst, u_long count) {
#if TRACE > 0
printf("WdCopy from %x to %x for %d\n", src, dst, count);
#endif
  assert(count <= 1514);
  if (boardid & SLOT_16BIT)
    outb(WD_BASEREG + WD_REG5,
	 WD_LAAR_MEM16E|WD_LAAR_LAN16E|WD_LAAR_A19);
  PhysBcopy(src, dst, count);
  if (boardid & SLOT_16BIT)
    outb(WD_BASEREG + WD_REG5, WD_LAAR_LAN16E|WD_LAAR_A19);
}

/*
 * Send an ethernet packet to destination 'dest'
 */
void
EtherSend(packet_t *pkt, u_short proto, u_char *dest) {
  ethhdr_t *ep;

  pkt->pkt_len += sizeof(ethhdr_t);
  pkt->pkt_offset -= sizeof(ethhdr_t);
  ep = (ethhdr_t *) pkt->pkt_offset;
  ep->eth_proto = htons(proto);
  bcopy((char *)dest, (char *)ep->eth_dst, ETH_ADDRSIZE);
  bcopy((char *)eth_myaddr, (char *)ep->eth_src, ETH_ADDRSIZE);
#if 0
DUMP_STRUCT("ethhdr_t", ep, sizeof(ethhdr_t));
#endif
  if (pkt->pkt_len < 60)
    pkt->pkt_len = 60;

#if TRACE > 0
  {
    int i;
DUMP_STRUCT("EtherSend: pkt", pkt->pkt_offset, pkt->pkt_len);
#if 0
    for(i=0; i<(pkt->pkt_len<MDUMP?pkt->pkt_len:MDUMP); i++) printe("%x ", *((u_char*)(pkt->pkt_offset)+i));
    printe("\n");
#endif
  }
#endif

#if 0
printe("EtherSend: WdCopy from %x to %x for %d\n", LA(pkt->pkt_offset), dpc.dc_mem + (dpc.dc_tpsr << 8), (u_long)pkt->pkt_len);
#endif
  WdCopy(LA(pkt->pkt_offset),
	 dpc.dc_mem + (dpc.dc_tpsr << 8), (u_long)pkt->pkt_len);
  outb(dpc.dc_reg + DP_TPSR, dpc.dc_tpsr);
  outb(dpc.dc_reg + DP_TBCR0, (pkt->pkt_len & 0xFF));
  outb(dpc.dc_reg + DP_TBCR1, (pkt->pkt_len >> 8) & 0xFF);
  outb(dpc.dc_reg + DP_CR, CR_TXP);

#if 0
  printe("Ethersend: outb(%x, %x)\n", dpc.dc_reg + DP_TPSR, dpc.dc_tpsr);
  printe("Ethersend: outb(%x, %x)\n", dpc.dc_reg + DP_TBCR0, (pkt->pkt_len & 0xFF));
  printe("Ethersend: outb(%x, %x)\n", dpc.dc_reg + DP_TBCR1, (pkt->pkt_len >> 8) & 0xFF);
  printe("Ethersend: outb(%x, %x)\n", dpc.dc_reg + DP_CR, CR_TXP);
#endif
}

/*
 * Copy dp8390 packet header for observation
 */
static void
GetHeader(u_long haddr, dphdr_t *dph) {
#if TRACE > 0
printe("GetHeader: WdCopy from %x to %x for %d\n", haddr, LA(dph), sizeof(dphdr_t));
#endif
  WdCopy(haddr, LA(dph), sizeof(dphdr_t));
#if 0
DUMP_STRUCT("GetHeader: dphdr_t", dph, sizeof(dphdr_t));
#endif
}

/*
 * Poll the dp8390 just see if there's an Ethernet packet
 * available. If there is, its contents is returned in a
 * pkt structure, otherwise a nil pointer is returned.
 */
packet_t *
EtherReceive(void) {
  u_char pageno, curpage, nextpage;
  int dpreg = dpc.dc_reg;
  packet_t *pkt;
  dphdr_t dph;
  u_long addr;

  pkt = (packet_t *)0;
  if (inb(dpreg + DP_RSR) & RSR_PRX) {
    /* get current page numbers */
    pageno = inb(dpreg + DP_BNRY) + 1;
    if (pageno == dpc.dc_pstop)
      pageno = dpc.dc_pstart;
    outb(dpreg + DP_CR, CR_PS_P1);
    curpage = inb(dpreg + DP_CURR);
    outb(dpreg + DP_CR, CR_PS_P0);
    if (pageno == curpage)
      return (packet_t *) 0;

    /* get packet header */
    addr = dpc.dc_mem + (pageno << 8);
    GetHeader(addr, &dph);
    nextpage = dph.dh_next;

    /* allocate packet */
    pkt = PktAlloc(0);
#if 0
printe("EtherReceive: allocated pkt %x\n", pkt);
#endif
    pkt->pkt_len = ((dph.dh_rbch & 0xFF) << 8) | (dph.dh_rbcl & 0xFF);
    pkt->pkt_len -= sizeof(dphdr_t);
    if (pkt->pkt_len > 1514) /* bug in dp8390 */
      pkt->pkt_len = 1514;

#if TRACE > 0
    {
      int i;
      printe("EtherReceive %d bytes: ", pkt->pkt_len);
#if 0
      for(i=0; i<(pkt->pkt_len<MDUMP?pkt->pkt_len:MDUMP); i++) printe("%x ", *((u_char*)pkt+i));
#else
      DUMP_STRUCT("", pkt, pkt->pkt_len);
#endif
      printe("\n");
    }
#endif

    /*
     * The dp8390 maintains a circular buffer of pages (256 bytes)
     * in which incomming ethernet packets are stored. The following
     * if detects wrap arounds, and copies the ethernet packet to
     * our local buffer in two chunks if necesarry.
     */
    assert(pkt->pkt_offset);
    assert(pkt->pkt_len <= (6 << 8));
    if (nextpage < pageno && nextpage > dpc.dc_pstart) {
      u_long nbytes = ((dpc.dc_pstop - pageno) << 8) - sizeof(dphdr_t);

      assert(nbytes <= (6 << 8));
#if TRACE > 0
printe("EtherReceive1: WdCopy from %x to %x for %x\n", addr + sizeof(dphdr_t), LA(pkt->pkt_offset), nbytes);
#endif
      WdCopy(addr + sizeof(dphdr_t),
	     LA(pkt->pkt_offset), nbytes);
      if ((pkt->pkt_len - nbytes) > 0)
	/* TBD - this OK? */
#if TRACE > 0
printe("EtherReceive2: WdCopy from %x to %x for %x\n",dpc.dc_mem + (dpc.dc_pstart << 8), LA(pkt->pkt_offset) + nbytes, pkt->pkt_len - nbytes);
#endif
	WdCopy(dpc.dc_mem + (dpc.dc_pstart << 8),
	       LA(pkt->pkt_offset) + nbytes,
	       pkt->pkt_len - nbytes); 
    } else {
#if TRACE > 0
printe("EtherReceive3: WdCopy from %x to %x for %x\n", addr + sizeof(dphdr_t), LA(pkt->pkt_offset), (u_long)pkt->pkt_len);
#endif
      WdCopy(addr + sizeof(dphdr_t),
	     LA(pkt->pkt_offset), (u_long)pkt->pkt_len);
    }

    /* release occupied pages */
    if (nextpage == dpc.dc_pstart)
      nextpage = dpc.dc_pstop;
    outb(dpreg + DP_BNRY, nextpage - 1);
  }

  return pkt;
}

/*
 * Print an ethernet address in human readable form
 */
void
EtherPrintAddr(u_char *addr) {
  printf("%x:%x:%x:%x:%x:%x",
	 addr[0] & 0xFF, addr[1] & 0xFF, addr[2] & 0xFF,
	 addr[3] & 0xFF, addr[4] & 0xFF, addr[5] & 0xFF);
}
