sys/arch/i386/stand/lib/netif/elink3.c
/*	$NetBSD: elink3.c,v 1.4 2008/12/14 18:46:33 christos Exp $	*/

/* stripped down from freebsd:sys/i386/netboot/3c509.c */

/**************************************************************************
NETBOOT -  BOOTP/TFTP Bootstrap Program

Author: Martin Renters.
  Date: Mar 22 1995

 This code is based heavily on David Greenman's if_ed.c driver and
  Andres Vega Garcia's if_ep.c driver.

 Copyright (C) 1993-1994, David Greenman, Martin Renters.
 Copyright (C) 1993-1995, Andres Vega Garcia.
 Copyright (C) 1995, Serge Babkin.
  This software may be used, modified, copied, distributed, and sold, in
  both source and binary form provided that the above copyright and these
  terms are retained. Under no circumstances are the authors responsible for
  the proper functioning of this software, nor do the authors assume any
  responsibility for damages incurred with its use.

3c509 support added by Serge Babkin (babkin@hq.icb.chel.su)

3c509.c,v 1.2 1995/05/30 07:58:52 rgrimes Exp

***************************************************************************/

#include <sys/types.h>
#include <machine/pio.h>

#include <lib/libsa/stand.h>

#include <libi386.h>

#include "etherdrv.h"
#include "3c509.h"

extern unsigned short eth_base;

extern u_char eth_myaddr[6];

void
epstop(void)
{

	/* stop card */
	outw(BASE + EP_COMMAND, RX_DISABLE);
	outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK);
	while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS);

	outw(BASE + EP_COMMAND, TX_DISABLE);
	outw(BASE + EP_COMMAND, STOP_TRANSCEIVER);

	outw(BASE + EP_COMMAND, RX_RESET);
	outw(BASE + EP_COMMAND, TX_RESET);

	outw(BASE + EP_COMMAND, C_INTR_LATCH);
	outw(BASE + EP_COMMAND, SET_RD_0_MASK);
	outw(BASE + EP_COMMAND, SET_INTR_MASK);
	outw(BASE + EP_COMMAND, SET_RX_FILTER);
}

void
EtherStop(void)
{

	epstop();
	outw(BASE + EP_COMMAND, GLOBAL_RESET);
	delay(100000);
}

/**************************************************************************
ETH_RESET - Reset adapter
***************************************************************************/
void
epreset(void)
{
	int i;

	/***********************************************************
			Reset 3Com 509 card
	*************************************************************/

	epstop();

	/*
	 * initialize card
	*/
	while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS)
		continue;

	GO_WINDOW(0);

	/* Disable the card */
	outw(BASE + EP_W0_CONFIG_CTRL, 0);

	/* Configure IRQ to none */
	outw(BASE + EP_W0_RESOURCE_CFG, SET_IRQ(0));

	/* Enable the card */
	outw(BASE + EP_W0_CONFIG_CTRL, ENABLE_DRQ_IRQ);

	GO_WINDOW(2);

	/* Reload the ether_addr. */
	for (i = 0; i < 6; i++)
		outb(BASE + EP_W2_ADDR_0 + i, eth_myaddr[i]);

	outw(BASE + EP_COMMAND, RX_RESET);
	outw(BASE + EP_COMMAND, TX_RESET);

	/* Window 1 is operating window */
	GO_WINDOW(1);
	for (i = 0; i < 31; i++)
		inb(BASE + EP_W1_TX_STATUS);

	/* get rid of stray intr's */
	outw(BASE + EP_COMMAND, ACK_INTR | 0xff);

	outw(BASE + EP_COMMAND, SET_RD_0_MASK | S_5_INTS);

	outw(BASE + EP_COMMAND, SET_INTR_MASK);

	outw(BASE + EP_COMMAND, SET_RX_FILTER | FIL_INDIVIDUAL |
	    FIL_BRDCST);

	/* configure BNC */
	if (ether_medium == ETHERMEDIUM_BNC) {
		outw(BASE + EP_COMMAND, START_TRANSCEIVER);
		delay(1000);
	}
	/* configure UTP */
	if (ether_medium == ETHERMEDIUM_UTP) {
		GO_WINDOW(4);
		outw(BASE + EP_W4_MEDIA_TYPE, ENABLE_UTP);
		GO_WINDOW(1);
	}

	/* start tranciever and receiver */
	outw(BASE + EP_COMMAND, RX_ENABLE);
	outw(BASE + EP_COMMAND, TX_ENABLE);

	/* set early threshold for minimal packet length */
	outw(BASE + EP_COMMAND, SET_RX_EARLY_THRESH | 64);

	outw(BASE + EP_COMMAND, SET_TX_START_THRESH | 16);
}

/**************************************************************************
ETH_TRANSMIT - Transmit a frame
***************************************************************************/
static const char padmap[] = {
	0, 3, 2, 1};

int
EtherSend(char *pkt, int len)
{
	int pad;
	int status;

#ifdef EDEBUG
	printf("{l=%d}", len);
#endif

	pad = padmap[len & 3];

	/*
	* The 3c509 automatically pads short packets to minimum ethernet length,
	* but we drop packets that are too large. Perhaps we should truncate
	* them instead?
	*/
	if (len + pad > ETHER_MAX_LEN) {
		return -1;
	}

	/* drop acknowledgements */
	while ((status = inb(BASE + EP_W1_TX_STATUS)) & TXS_COMPLETE ) {
		if (status & (TXS_UNDERRUN | TXS_MAX_COLLISION |
			TXS_STATUS_OVERFLOW)) {
			outw(BASE + EP_COMMAND, TX_RESET);
			outw(BASE + EP_COMMAND, TX_ENABLE);
		}

		outb(BASE + EP_W1_TX_STATUS, 0x0);
	}

	while (inw(BASE + EP_W1_FREE_TX) < len + pad + 4) {
		/* no room in FIFO */
		continue;
	}

	outw(BASE + EP_W1_TX_PIO_WR_1, len);
	outw(BASE + EP_W1_TX_PIO_WR_1, 0x0);	/* Second dword meaningless */

	/* write packet */
	outsw(BASE + EP_W1_TX_PIO_WR_1, pkt, len / 2);
	if (len & 1)
		outb(BASE + EP_W1_TX_PIO_WR_1, *(pkt + len - 1));

	while (pad--)
		outb(BASE + EP_W1_TX_PIO_WR_1, 0);	/* Padding */

	/* timeout after sending */
	delay(1000);
	return len;
}

/**************************************************************************
ETH_POLL - Wait for a frame
***************************************************************************/
int
EtherReceive(char *pkt, int maxlen)
{
	/* common variables */
	int len;
	/* variables for 3C509 */
	short status, cst;
	register short rx_fifo;

	cst = inw(BASE + EP_STATUS);

#ifdef EDEBUG
	if (cst & 0x1FFF)
		printf("-%x-",cst);
#endif

	if ((cst & (S_RX_COMPLETE|S_RX_EARLY)) == 0) {
		/* acknowledge  everything */
		outw(BASE + EP_COMMAND, ACK_INTR| (cst & S_5_INTS));
		outw(BASE + EP_COMMAND, C_INTR_LATCH);

		return 0;
	}

	status = inw(BASE + EP_W1_RX_STATUS);
#ifdef EDEBUG
	printf("*%x*",status);
#endif

	if (status & ERR_RX) {
		outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK);
		return 0;
	}

	rx_fifo = status & RX_BYTES_MASK;
	if (rx_fifo == 0)
		return 0;

	if (rx_fifo > maxlen)
		goto zulang;

	/* read packet */
#ifdef EDEBUG
	printf("[l=%d",rx_fifo);
#endif
	insw(BASE + EP_W1_RX_PIO_RD_1, pkt, rx_fifo / 2);
	if (rx_fifo & 1)
		pkt[rx_fifo-1] = inb(BASE + EP_W1_RX_PIO_RD_1);
	len = rx_fifo;

	for (;;) {
		status = inw(BASE + EP_W1_RX_STATUS);
#ifdef EDEBUG
		printf("*%x*",status);
#endif
		rx_fifo = status & RX_BYTES_MASK;

		if (rx_fifo > 0) {
			if ((len + rx_fifo) > maxlen)
				goto zulang;

			insw(BASE + EP_W1_RX_PIO_RD_1, pkt + len, rx_fifo / 2);
			if (rx_fifo & 1)
				pkt[len + rx_fifo-1] = inb(BASE + EP_W1_RX_PIO_RD_1);
			len += rx_fifo;
#ifdef EDEBUG
			printf("+%d",rx_fifo);
#endif
		}

		if ((status & RX_INCOMPLETE) == 0) {
#ifdef EDEBUG
			printf("=%d",len);
#endif
			break;
		}

		delay(1000);
	}

	/* acknowledge reception of packet */
	outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK);
	while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS)
		continue;

	return len;

 zulang:
	outw(BASE + EP_COMMAND, RX_DISCARD_TOP_PACK);
	while (inw(BASE + EP_STATUS) & S_COMMAND_IN_PROGRESS)
		continue;
	return 0;
}

/*************************************************************************
	3Com 509 - specific routines
**************************************************************************/

static int
eeprom_rdy(void)
{
	int i;

	for (i = 0; is_eeprom_busy(IS_BASE) && i < MAX_EEPROMBUSY; i++);
	if (i >= MAX_EEPROMBUSY) {
		printf("3c509: eeprom failed to come ready.\r\n");
		return 0;
	}
	return 1;
}

/*
 * get_e: gets a 16 bits word from the EEPROM. we must have set the window
 * before
 */
int
ep_get_e(int offset)
{
	if (!eeprom_rdy())
		return 0xffff;
	outw(IS_BASE + EP_W0_EEPROM_COMMAND, EEPROM_CMD_RD | offset);
	if (!eeprom_rdy())
		return 0xffff;
	return inw(IS_BASE + EP_W0_EEPROM_DATA);
}
