int eth_close(void)
{
	int spi_isopen;

	TRACE("eth_close()\n");
	/* see whether SPI was already set up (closing before opening) */
	spi_isopen = ((PCB_PINSEL0 & (0x33 << 8)) == (0x11 << 8));
	if (!spi_isopen) {
		setup_iopins();
		spi0_init(SPI_FREQ_HIGH, 0, 0, 0, 1);
	} /* if */

	eth_setstatus(ETH_STAT_OFF);	/* turn all LEDs off */
	enc_regset(ECON2, ECON2_PWRSV);	/* set power-save mode */

	/* close SPI if it was opened by this routine */
	if (!spi_isopen)
		spi0_close();

	return IP_ERR_NONE;
}

int eth_setstatus(int status)
{
	switch (status) {
	case ETH_STAT_OFF:
		/* both LEDs off */
		enc_putPHY(PHLCON, 0x0992);
		break;
	case ETH_NO_ADDR:
		/* until an IP address is set, blink LED B (LED A=link status) */
		enc_putPHY(PHLCON, 0x04a2);
		break;
	case ETH_ADDR_SET:
		/* LED A=link status, LED B=receive+transmit status */
		enc_putPHY(PHLCON, 0x0472);
		break;
	default:
		return IP_ERR_PARAM;
	} /* switch */
	return IP_ERR_NONE;
}

/** Enable/disable multicast
 * Multi-cast is disabled by default. You must enable it for receiving
 * multi-cast packets, such as those sent out by RTP servers.
 */
int eth_enablemulticast(int enable)
{
	unsigned char value;
	enc_setbank(1);
	value = enc_get8(ERxFCon, 0);
	if (enable)
		value |= ERxFCon_MCEN;
	else
		value &= ~ERxFCon_MCEN;
	enc_put8(ERxFCon, value);
	return IP_ERR_NONE;
}

static int enc_reset(int type)
{
	switch (type) {
	case ENC_SOFTRESET:
		spi0_starttransfer(ENC_SELECT);
		spi0_put(SC);		/* send the System Command (Reset) */
		spi0_stoptransfer(ENC_SELECT);
		break;
#if defined ENC_RESET
	case ENC_HARDRESET:
		GPIO0_IOCLR = (1 << ENC_RESET);
		rtc_mdelay(10);
		GPIO0_IOSET = (1 << ENC_RESET);
		break;
#endif
	default:
		return IP_ERR_PARAM;
	} /* switch */
	rtc_mdelay(10);
	return IP_ERR_NONE;
}

/** eth_read() reads data from the ENC28J60 buffer (FIFO).
 * The "read pointer" is updated while reading, so you should reset it
 * if you wish to read the same data again. See functions eth_getreadptr()
 * and eth_setreadptr() for details.
 */
int eth_read(void *buffer, int length)
{
	unsigned char *ptr = buffer;
	spi0_starttransfer(ENC_SELECT);
	spi0_put(RBM);
	while (length-- > 0)
		*ptr++ = spi0_put(0x00);
	spi0_stoptransfer(ENC_SELECT);
	return IP_ERR_NONE;
}

/** eth_write() store data in the buffer (FIFO).
 * The "write pointer" is updated.
 */
int eth_write(const void *buffer, int length)
{
	const unsigned char *ptr = buffer;
	spi0_starttransfer(ENC_SELECT);
	spi0_put(WBM);
	while (length-- > 0)
		spi0_put(*ptr++);
	spi0_stoptransfer(ENC_SELECT);
	return IP_ERR_NONE;
}

int eth_header(const unsigned char dest[6], const unsigned char source[6], unsigned short protocol)
{
	ETH_HDR eth_hdr;
	unsigned char value;

	enc_setbank(0);
	enc_put16(EWrPtL, TXSTART);

	/* write the per packet option byte; we use the default values set in chip earlier */
	value = 0;
	eth_write(&value, 1);

	/* write the Ethernet frame */
	memcpy(&eth_hdr.destination, dest, 6);
	memcpy(&eth_hdr.sender, source, 6);
	eth_hdr.protocol = ip_swap16(protocol);
	eth_write(&eth_hdr, ETH_HDR_SIZE);

	return ETH_HDR_SIZE;
}

#if defined TRACING
static void eth_dumpbuffer(int start, int length)
{
	unsigned char val, str[20];
	int idx;

	TRACE("buffer = "); TRACEVALX(start); TRACE("; length ="); TRACEVAL(length); TRACE("\n");
	//??? end_getreadptr() + restore at end
	enc_setbank(0);
	enc_put16(ERdPtL, start);
	idx = 0;
	while ((length & 0x0f) != 0)
		length++;			/* round up to multiple of 16 */
	while (length-- > 0) {
		eth_read(&val, 1);
		if (val < 16)
			TRACE("0");
		TRACEVALX(val);
		TRACE(" ");
		str[idx++] = (val >= ' ' && val <= 127) ? (unsigned char)val : '.';
		if (idx >= 16) {
			str[idx++] = '\n';
			str[idx] = '\0';
			TRACE(str);
			idx = 0;
		} /* if */
	} /* while */
}
#endif

int eth_ftpinfo_store(const FTPCFG *config)
{
	enc_setbank(0);
	enc_put16(EWrPtL, FTP_AREA);
	return eth_write(config, sizeof(FTPCFG));
}

int eth_ftpinfo_settimeout(short timeout)
{
	enc_setbank(0);
	enc_put16(EWrPtL, FTP_AREA + offsetof(FTPCFG, timeout));
	return eth_write(&timeout, sizeof(short));
}

int eth_url_get(char name[URL_MAXLENGTH])
{
	int err;
	unsigned short offs = enc_getreadptr();

	enc_put16(ERdPtL, URL_AREA);
	err = eth_read(name, URL_MAXLENGTH);
	enc_setreadptr(offs);
	/* force a zero terminator */
	name[URL_MAXLENGTH - 1] = '\0';
	return err;
}

int eth_url_store(const char name[URL_MAXLENGTH])
{
	int length = strlen(name);
	if (length > URL_MAXLENGTH - 1)
		length = URL_MAXLENGTH - 1;
	enc_setbank(0);
	enc_put16(EWrPtL, URL_AREA);
	return eth_write(name, length + 1);
}

int eth_snmpnames_get(char readonly[HOSTNAME_LENGTH+1], char readwrite[HOSTNAME_LENGTH+1])
{
	char name[HOSTNAME_LENGTH + 1];
	unsigned short offs = enc_getreadptr();
	enc_put16(ERdPtL, SNMP_AREA);

	eth_read(name, HOSTNAME_LENGTH);
	name[HOSTNAME_LENGTH] = '\0';	/* force zero-terminate */
	if (readonly != NULL)
		strcpy(readonly, name);

	eth_read(name, HOSTNAME_LENGTH);
	name[HOSTNAME_LENGTH] = '\0';	/* force zero-terminate */
	if (readwrite != NULL)
		strcpy(readwrite, name);

	enc_setreadptr(offs);
	return IP_ERR_NONE;
}

int eth_snmpnames_store(const char readonly[HOSTNAME_LENGTH+1], const char readwrite[HOSTNAME_LENGTH+1])
{
	enc_setbank(0);
	if (readonly != NULL) {
		enc_put16(EWrPtL, SNMP_AREA);
		eth_write(readonly, HOSTNAME_LENGTH);
	} /* if */
	if (readwrite != NULL) {
		enc_put16(EWrPtL, SNMP_AREA + HOSTNAME_LENGTH);
		eth_write(readwrite, HOSTNAME_LENGTH);
	} /* if */
	return IP_ERR_NONE;
}

/** enc_regset() sets bits in the specified register.
 * There is a separate "clear" operation.
 */
static void enc_regset(unsigned char address, unsigned char data)
{
	spi0_starttransfer(ENC_SELECT);
	spi0_put(BFS | address);
	spi0_put(data);
	spi0_stoptransfer(ENC_SELECT);
}

/** enc_regclr() clears bits in the specified register.
 * There is a separate "set" operation.
 */
static void enc_regclr(unsigned char address, unsigned char data)
{
	spi0_starttransfer(ENC_SELECT);
	spi0_put(BFC | address);
	spi0_put(data);
	spi0_stoptransfer(ENC_SELECT);
}

/** enc_setbank() sets the current register bank
 */
static void enc_setbank(unsigned char bank)
{
	/* first clear all, then set */
	enc_regclr(ECON1, ECON1_BSEL1 | ECON1_BSEL0);
	enc_regset(ECON1, bank);
}

/** enc_get8() reads a byte from a register.
 * \param address	The register address.
 * \param waitstate	Set to 0 for ETH registers and to 1 for MAC/MII registers
 *					(these registers need a dummy read).
 */
static unsigned char enc_get8(unsigned char address, int waitstate)
{
	unsigned char value;

	spi0_starttransfer(ENC_SELECT);
	spi0_put(RCR | address);
	if (waitstate)
		spi0_put(0x00);			/* send a dummy byte (datasheet p. 27) */
	value = spi0_put(0x00);
	spi0_stoptransfer(ENC_SELECT);
	return value;
}

