/* $NetBSD: fxp.c,v 1.3 2011/10/30 21:08:33 phx Exp $ */

/*
 * most of the following code was imported from dev/ic/i82557.c; the
 * original copyright notice is as below.
 */

/*-
 * Copyright (c) 1997, 1998, 1999, 2001, 2002 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
 * NASA Ames Research Center.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Copyright (c) 1995, David Greenman
 * Copyright (c) 2001 Jonathan Lemon <jlemon@freebsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	Id: if_fxp.c,v 1.113 2001/05/17 23:50:24 jlemon
 */

#include <sys/param.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>

#include <lib/libsa/stand.h>
#include <lib/libsa/net.h>

#include <dev/ic/i82557reg.h>

#include "globals.h"

#define FRAMESIZE 1536

/*
 * 82559ER 8086.1209/1229
 *
 * - reverse endian access for 16bit/32bit register.
 * - no vtophys() translation, vaddr_t == paddr_t. 
 * - PIPT writeback cache aware.
 */
#define CSR_WRITE_1(l, r, v)	out8((l)->iobase+(r), (v))
#define CSR_READ_1(l, r)	in8((l)->iobase+(r))
#define CSR_WRITE_2(l, r, v)	out16rb((l)->iobase+(r), (v))
#define CSR_READ_2(l, r)	in16rb((l)->iobase+(r))
#define CSR_WRITE_4(l, r, v) 	out32rb((l)->iobase+(r), (v))
#define CSR_READ_4(l, r)	in32rb((l)->iobase+(r))
#define VTOPHYS(va) 		(uint32_t)(va)
#define DEVTOV(pa) 		(uint32_t)(pa)
#define wbinv(adr, siz)		_wbinv(VTOPHYS(adr), (uint32_t)(siz))
#define inv(adr, siz)		_inv(VTOPHYS(adr), (uint32_t)(siz))
#define DELAY(n)		delay(n)
#define ALLOC(T,A)		(T *)allocaligned(sizeof(T),(A))

struct txdesc {
	volatile uint16_t cb_status;
	volatile uint16_t cb_command; 
	volatile uint32_t link_addr;
	volatile uint32_t tbd_array_addr;
	volatile uint16_t byte_count;
	volatile uint8_t tx_threshold;
	volatile uint8_t tbd_number;
	volatile uint32_t tx_buf_addr0;
	volatile uint32_t tx_buf_size0;
	volatile uint32_t tx_buf_addr1;
	volatile uint32_t tx_buf_size1;
}; /* mimic extended TxCB layout */

struct rxdesc {
	volatile uint16_t rfa_status;
	volatile uint16_t rfa_control;
	volatile uint32_t link_addr; 
	volatile uint32_t rbd_addr;
	volatile uint16_t actual_size; 
	volatile uint16_t size;
}; /* 16B rfa */

struct local {
	struct txdesc txd;
	uint8_t store[sizeof(struct rxdesc) + FRAMESIZE];
	unsigned iobase;
	unsigned eeprom_addr;
};

static void autosize_eeprom(struct local *);
static int read_eeprom(struct local *, int);
static void fxp_scb_wait(struct local *);
#ifdef DEBUG
static int fxp_mdi_read(struct local *, int, int);
#endif

/*
 * Template for default configuration parameters.
 * See struct fxp_cb_config for the bit definitions.
 */
static uint8_t fxp_cb_config_template[] = {
	0x0, 0x0,		/* cb_status */
	0x80, 0x2,		/* cb_command */
	0xff, 0xff, 0xff, 0xff, /* link_addr */
	0x16,	/*  0 */
	0x8,	/*  1 */
	0x0,	/*  2 */
	0x0,	/*  3 */
	0x0,	/*  4 */
	0x80,	/*  5 */
	0xb2,	/*  6 */
	0x3,	/*  7 */
	0x1,	/*  8 */
	0x0,	/*  9 */
	0x26,	/* 10 */
	0x0,	/* 11 */
	0x60,	/* 12 */
	0x0,	/* 13 */
	0xf2,	/* 14 */
	0x48,	/* 15 */
	0x0,	/* 16 */
	0x40,	/* 17 */
	0xf3,	/* 18 */
	0x0,	/* 19 */
	0x3f,	/* 20 */
	0x5	/* 21 */
};

static struct fxp_cb_config store_cbc;
static struct fxp_cb_ias store_cbi;

int
fxp_match(unsigned tag, void *data)
{
	unsigned v;

	v = pcicfgread(tag, PCI_ID_REG);
	switch (v) {
	case PCI_DEVICE(0x8086, 0x1209):
	case PCI_DEVICE(0x8086, 0x1229):
		return 1;
	}
	return 0;
}

void *
fxp_init(unsigned tag, void *data)
{
	struct local *sc;
	uint8_t *en = data;
	struct fxp_cb_config *cbp = &store_cbc;
	struct fxp_cb_ias *cb_ias = &store_cbi;
	struct rxdesc *rfa;
	unsigned v, i;

	sc = ALLOC(struct local, sizeof(struct txdesc)); /* desc alignment */
	memset(sc, 0, sizeof(struct local));
	sc->iobase = DEVTOV(pcicfgread(tag, 0x10)); /* use mem space */

	CSR_WRITE_4(sc, FXP_CSR_PORT, FXP_PORT_SELECTIVE_RESET);
	DELAY(100);	

	autosize_eeprom(sc);
	v = read_eeprom(sc, 0); en[0] = v; en[1] = v >> 8;
	v = read_eeprom(sc, 1); en[2] = v; en[3] = v >> 8;
	v = read_eeprom(sc, 2); en[4] = v; en[5] = v >> 8;

	printf("MAC address %02x:%02x:%02x:%02x:%02x:%02x, ",
		en[0], en[1], en[2], en[3], en[4], en[5]);

	DPRINTF(("PHY %d (%04x.%04x)\n", fxp_mdi_read(sc, 1, 18),
	   fxp_mdi_read(sc, 1, 2), fxp_mdi_read(sc, 1, 3)));

	/* 
	 * Initialize base of CBL and RFA memory. Loading with zero
	 * sets it up for regular linear addressing.
	 */
	CSR_WRITE_4(sc, FXP_CSR_SCB_GENERAL, 0);
	CSR_WRITE_1(sc, FXP_CSR_SCB_COMMAND, FXP_SCB_COMMAND_CU_BASE);

	fxp_scb_wait(sc);
	CSR_WRITE_1(sc, FXP_CSR_SCB_COMMAND, FXP_SCB_COMMAND_RU_BASE);

	/*
	 * This memcpy is kind of disgusting, but there are a bunch of must be
	 * zero and must be one bits in this structure and this is the easiest
	 * way to initialize them all to proper values.
	 */
	memcpy(cbp, fxp_cb_config_template, sizeof(fxp_cb_config_template));

#define prm 0
#define phy_10Mbps_only 0
#define all_mcasts 0
	cbp->cb_status =	0;
	cbp->cb_command =	htole16(FXP_CB_COMMAND_CONFIG |
				    FXP_CB_COMMAND_EL);
	cbp->link_addr =	-1;	/* (no) next command */
	cbp->byte_count =	22;	/* (22) bytes to config */
	cbp->rx_fifo_limit =	8;	/* rx fifo threshold (32 bytes) */
	cbp->tx_fifo_limit =	0;	/* tx fifo threshold (0 bytes) */
	cbp->adaptive_ifs =	0;	/* (no) adaptive interframe spacing */
	cbp->rx_dma_bytecount = 0;	/* (no) rx DMA max */
	cbp->tx_dma_bytecount = 0;	/* (no) tx DMA max */
	cbp->dma_mbce =		0;	/* (disable) dma max counters */
	cbp->late_scb =		0;	/* (don't) defer SCB update */
	cbp->tno_int_or_tco_en = 0;	/* (disable) tx not okay interrupt */
	cbp->ci_int =		0;	/* interrupt on CU not active */
	cbp->save_bf =		prm;	/* save bad frames */
	cbp->disc_short_rx =	!prm;	/* discard short packets */
	cbp->underrun_retry =	1;	/* retry mode (1) on DMA underrun */
	cbp->mediatype =	!phy_10Mbps_only; /* interface mode */
	cbp->nsai =		1;     /* (don't) disable source addr insert */
	cbp->preamble_length =	2;	/* (7 byte) preamble */
	cbp->loopback =		0;	/* (don't) loopback */
	cbp->linear_priority =	0;	/* (normal CSMA/CD operation) */
	cbp->linear_pri_mode =	0;	/* (wait after xmit only) */
	cbp->interfrm_spacing = 6;	/* (96 bits of) interframe spacing */
	cbp->promiscuous =	prm;	/* promiscuous mode */
	cbp->bcast_disable =	0;	/* (don't) disable broadcasts */
	cbp->crscdt =		0;	/* (CRS only) */
	cbp->stripping =	!prm;	/* truncate rx packet to byte count */
	cbp->padding =		1;	/* (do) pad short tx packets */
	cbp->rcv_crc_xfer =	0;	/* (don't) xfer CRC to host */
	cbp->force_fdx =	0;	/* (don't) force full duplex */
	cbp->fdx_pin_en =	1;	/* (enable) FDX# pin */
	cbp->multi_ia =		0;	/* (don't) accept multiple IAs */
	cbp->mc_all =		all_mcasts;/* accept all multicasts */
#undef prm
#undef phy_10Mbps_only
#undef all_mcasts

	wbinv(cbp, sizeof(*cbp));
	fxp_scb_wait(sc);
	CSR_WRITE_4(sc, FXP_CSR_SCB_GENERAL, VTOPHYS(cbp));
	CSR_WRITE_1(sc, FXP_CSR_SCB_COMMAND, FXP_SCB_COMMAND_CU_START);
	i = 1000;
	while (!(le16toh(cbp->cb_status) & FXP_CB_STATUS_C) && --i > 0) {
		DELAY(1);
		inv(&cbp->cb_status, sizeof(cbp->cb_status));
	}
	if (i == 0)
		printf("cbp config timeout\n");

	/*
	 * Initialize the station address.
	 */
	cb_ias->cb_status = 0;
	cb_ias->cb_command = htole16(FXP_CB_COMMAND_IAS | FXP_CB_COMMAND_EL);
	cb_ias->link_addr = -1;
	memcpy(cb_ias->macaddr, en, 6);

	/*
	 * Start the IAS (Individual Address Setup) command/DMA.
	 */
	wbinv(cb_ias, sizeof(*cb_ias));
	fxp_scb_wait(sc);
	CSR_WRITE_4(sc, FXP_CSR_SCB_GENERAL, VTOPHYS(cb_ias));
	CSR_WRITE_1(sc, FXP_CSR_SCB_COMMAND, FXP_SCB_COMMAND_CU_START);

	i = 1000;
	while (!(le16toh(cb_ias->cb_status) & FXP_CB_STATUS_C) && --i > 0) {
		DELAY(1);
		inv(&cb_ias->cb_status, sizeof(cb_ias->cb_status));
	}
	if (i == 0)
		printf("ias config timeout\n");

	rfa = (struct rxdesc *)sc->store;
	rfa->rfa_status = 0;
	rfa->rfa_control = htole16(FXP_RFA_CONTROL_S);
	rfa->link_addr = htole32(VTOPHYS(rfa));
	rfa->rbd_addr = -1;
	rfa->actual_size = 0;
	rfa->size = htole16(sizeof(sc->store) - sizeof(struct rxdesc));
	wbinv(rfa, sizeof(sc->store));

	fxp_scb_wait(sc);
	CSR_WRITE_4(sc, FXP_CSR_SCB_GENERAL, VTOPHYS(rfa));
	CSR_WRITE_1(sc, FXP_CSR_SCB_COMMAND, FXP_SCB_COMMAND_RU_START);

	return sc;
}

int
fxp_send(void *dev, char *buf, unsigned len)
{
	struct local *l = dev;
	struct txdesc *txd;
	int loop;

	if (len > 1520)
		printf("fxp_send: len > 1520 (%u)\n", len);

	txd = &l->txd;
	txd->cb_status = 0;
	txd->cb_command =
	    htole16(FXP_CB_COMMAND_XMIT|FXP_CB_COMMAND_SF|FXP_CB_COMMAND_EL);
	txd->link_addr = -1;
	txd->tbd_array_addr = htole32(VTOPHYS(&txd->tx_buf_addr0));
	txd->tx_buf_addr0 = htole32(VTOPHYS(buf));
	txd->tx_buf_size0 = htole32(len);
	txd->byte_count = htole16(0x8000);
	txd->tx_threshold = 0x20;
	txd->tbd_number = 1;
	wbinv(buf, len);
	wbinv(txd, sizeof(*txd));

	fxp_scb_wait(l);
	CSR_WRITE_4(l, FXP_CSR_SCB_GENERAL, VTOPHYS(txd));
	CSR_WRITE_1(l, FXP_CSR_SCB_COMMAND, FXP_SCB_COMMAND_CU_START);
	
	loop = 10000;
	while (!(le16toh(txd->cb_status) & FXP_CB_STATUS_C) && --loop > 0) {
		DELAY(1);
		inv(txd, sizeof(struct txdesc));
	}
	if (loop == 0)
		printf("send timeout\n");

	return len;
}

int
fxp_recv(void *dev, char *buf, unsigned maxlen, unsigned timo)
{
	struct local *l = dev;
	struct rxdesc *rfa;
	unsigned bound, ruscus, len;

	fxp_scb_wait(l);
	CSR_WRITE_1(l, FXP_CSR_SCB_COMMAND, FXP_SCB_COMMAND_RU_RESUME);

	bound = 1000 * timo;
	do {
		ruscus = CSR_READ_1(l, FXP_CSR_SCB_RUSCUS);
		if (((ruscus >> 2) & 0x0f) != FXP_SCB_RUS_READY
		    && (((ruscus >> 2) & 0x0f) == FXP_SCB_RUS_SUSPENDED))
			goto gotone;
		DELAY(1000);	/* 1 milli second */
	} while (--bound > 0);
	errno = 0;
	return -1;
  gotone:
	rfa = (struct rxdesc *)l->store;
	inv(rfa, sizeof(l->store)); /* whole including received frame */
	if ((le16toh(rfa->rfa_status) & FXP_RFA_STATUS_C) == 0)
		return 0;
	len = le16toh(rfa->actual_size) & 0x7ff;
	if (len > maxlen)
		len = maxlen;
	memcpy(buf, &l->store[sizeof(struct rxdesc)], len);

	rfa->rfa_status = 0;
	rfa->rfa_control = htole16(FXP_RFA_CONTROL_S);
	rfa->actual_size = 0;
	wbinv(rfa, sizeof(struct rxdesc));
#if 0
	fxp_scb_wait(l);
	CSR_WRITE_1(l, FXP_CSR_SCB_COMMAND, FXP_SCB_COMMAND_RU_RESUME);
#endif
	return len;
}

static void
eeprom_shiftin(struct local *sc, int data, int len)
{
	uint16_t reg;
	int x;

	for (x = 1 << (len - 1); x != 0; x >>= 1) {
		DELAY(40);
		if (data & x)
			reg = FXP_EEPROM_EECS | FXP_EEPROM_EEDI;
		else
			reg = FXP_EEPROM_EECS;
		CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, reg);
		DELAY(40);
		CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL,
		    reg | FXP_EEPROM_EESK);
		DELAY(40);
		CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, reg);
	}
	DELAY(40);
}

void
autosize_eeprom(struct local *sc)
{
	int x;

	CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, FXP_EEPROM_EECS);
	DELAY(40);

	/* Shift in read opcode. */
	eeprom_shiftin(sc, FXP_EEPROM_OPC_READ, 3);

	/*
	 * Shift in address, wait for the dummy zero following a correct
	 * address shift.
	 */
	for (x = 1; x <= 8; x++) {
		CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, FXP_EEPROM_EECS);
		DELAY(40);
		CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL,
		    FXP_EEPROM_EECS | FXP_EEPROM_EESK);
		DELAY(40);
		if ((CSR_READ_2(sc, FXP_CSR_EEPROMCONTROL) &
		    FXP_EEPROM_EEDO) == 0)
			break;
		DELAY(40);
		CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, FXP_EEPROM_EECS);
		DELAY(40);
	}
	CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, 0);
	DELAY(40);
	if (x != 6 && x != 8)
		printf("fxp: strange EEPROM address size (%d)\n", x);
	else
		sc->eeprom_addr = x;
}

static int
read_eeprom(struct local *sc, int offset)
{
	uint16_t reg;
	int x, val;

	CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, FXP_EEPROM_EECS);

	/* Shift in read opcode. */
	eeprom_shiftin(sc, FXP_EEPROM_OPC_READ, 3);

	/* Shift in address. */
	eeprom_shiftin(sc, offset, sc->eeprom_addr);

	reg = FXP_EEPROM_EECS;
	val = 0;
	/*
	 * Shift out data.
	 */
	for (x = 16; x > 0; x--) {
		CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL,
		    reg | FXP_EEPROM_EESK);
		DELAY(1);
		if (CSR_READ_2(sc, FXP_CSR_EEPROMCONTROL) &
		    FXP_EEPROM_EEDO)
			val |= (1 << (x - 1));
		CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, reg);
		DELAY(1);
	}
	CSR_WRITE_2(sc, FXP_CSR_EEPROMCONTROL, 0);
	DELAY(1);

	return val;
}

static void
fxp_scb_wait(struct local *sc)
{
	int loop = 5000;

	while (CSR_READ_1(sc, FXP_CSR_SCB_COMMAND) && --loop > 0)
		DELAY(2);
	if (loop == 0)
		printf("SCB timeout\n");
}

#ifdef DEBUG
static int
fxp_mdi_read(struct local *sc, int phy, int reg)
{
	int count = 10000;
	int value;

	CSR_WRITE_4(sc, FXP_CSR_MDICONTROL,
	    (FXP_MDI_READ << 26) | (reg << 16) | (phy << 21));

	while (((value = CSR_READ_4(sc, FXP_CSR_MDICONTROL)) &
	    0x10000000) == 0 && count--)
		DELAY(10);

	if (count <= 0)
		printf("fxp_mdi_read: timed out\n");

	return (value & 0xffff);
}
#endif
