/* $Date: 2007-05-25 06:50:02 $ $RCSfile: mac.c,v $ $Revision: 1.1.1.1 $ */
#include "gmac.h"
#include "regs.h"
#include "fpga_defs.h"

#define MAC_CSR_INTERFACE_GMII      0x0
#define MAC_CSR_INTERFACE_TBI       0x1
#define MAC_CSR_INTERFACE_MII       0x2
#define MAC_CSR_INTERFACE_RMII      0x3

/* Chelsio's MAC statistics. */
struct mac_statistics {

	/* Transmit */
	u32 TxFramesTransmittedOK;
	u32 TxReserved1;
	u32 TxReserved2;
	u32 TxOctetsTransmittedOK;
	u32 TxFramesWithDeferredXmissions;
	u32 TxLateCollisions;
	u32 TxFramesAbortedDueToXSCollisions;
	u32 TxFramesLostDueToIntMACXmitError;
	u32 TxReserved3;
	u32 TxMulticastFrameXmittedOK;
	u32 TxBroadcastFramesXmittedOK;
	u32 TxFramesWithExcessiveDeferral;
	u32 TxPAUSEMACCtrlFramesTransmitted;

	/* Receive */
	u32 RxFramesReceivedOK;
	u32 RxFrameCheckSequenceErrors;
	u32 RxAlignmentErrors;
	u32 RxOctetsReceivedOK;
	u32 RxFramesLostDueToIntMACRcvError;
	u32 RxMulticastFramesReceivedOK;
	u32 RxBroadcastFramesReceivedOK;
	u32 RxInRangeLengthErrors;
	u32 RxTxOutOfRangeLengthField;
	u32 RxFrameTooLongErrors;
	u32 RxPAUSEMACCtrlFramesReceived;
};

static int static_aPorts[] = {
	FPGA_GMAC_INTERRUPT_PORT0,
	FPGA_GMAC_INTERRUPT_PORT1,
	FPGA_GMAC_INTERRUPT_PORT2,
	FPGA_GMAC_INTERRUPT_PORT3
};

struct _cmac_instance {
	u32 index;
};

static int mac_intr_enable(struct cmac *mac)
{
	u32 mac_intr;

	if (t1_is_asic(mac->adapter)) {
		/* ASIC */

		/* We don't use the on chip MAC for ASIC products. */
	} else {
		/* FPGA */

		/* Set parent gmac interrupt. */
		mac_intr = readl(mac->adapter->regs + A_PL_ENABLE);
		mac_intr |= FPGA_PCIX_INTERRUPT_GMAC;
		writel(mac_intr, mac->adapter->regs + A_PL_ENABLE);

		mac_intr = readl(mac->adapter->regs + FPGA_GMAC_ADDR_INTERRUPT_ENABLE);
		mac_intr |= static_aPorts[mac->instance->index];
		writel(mac_intr,
		       mac->adapter->regs + FPGA_GMAC_ADDR_INTERRUPT_ENABLE);
	}

	return 0;
}

static int mac_intr_disable(struct cmac *mac)
{
	u32 mac_intr;

	if (t1_is_asic(mac->adapter)) {
		/* ASIC */

		/* We don't use the on chip MAC for ASIC products. */
	} else {
		/* FPGA */

		/* Set parent gmac interrupt. */
		mac_intr = readl(mac->adapter->regs + A_PL_ENABLE);
		mac_intr &= ~FPGA_PCIX_INTERRUPT_GMAC;
		writel(mac_intr, mac->adapter->regs + A_PL_ENABLE);

		mac_intr = readl(mac->adapter->regs + FPGA_GMAC_ADDR_INTERRUPT_ENABLE);
		mac_intr &= ~(static_aPorts[mac->instance->index]);
		writel(mac_intr,
		       mac->adapter->regs + FPGA_GMAC_ADDR_INTERRUPT_ENABLE);
	}

	return 0;
}

static int mac_intr_clear(struct cmac *mac)
{
	u32 mac_intr;

	if (t1_is_asic(mac->adapter)) {
		/* ASIC */

		/* We don't use the on chip MAC for ASIC products. */
	} else {
		/* FPGA */

		/* Set parent gmac interrupt. */
		writel(FPGA_PCIX_INTERRUPT_GMAC,
		       mac->adapter->regs +  A_PL_CAUSE);
		mac_intr = readl(mac->adapter->regs + FPGA_GMAC_ADDR_INTERRUPT_CAUSE);
		mac_intr |= (static_aPorts[mac->instance->index]);
		writel(mac_intr,
		       mac->adapter->regs + FPGA_GMAC_ADDR_INTERRUPT_CAUSE);
	}

	return 0;
}

static int mac_get_address(struct cmac *mac, u8 addr[6])
{
	u32 data32_lo, data32_hi;

	data32_lo = readl(mac->adapter->regs
			  + MAC_REG_IDLO(mac->instance->index));
	data32_hi = readl(mac->adapter->regs
			  + MAC_REG_IDHI(mac->instance->index));

	addr[0] = (u8) ((data32_hi >> 8) & 0xFF);
	addr[1] = (u8) ((data32_hi) & 0xFF);
	addr[2] = (u8) ((data32_lo >> 24) & 0xFF);
	addr[3] = (u8) ((data32_lo >> 16) & 0xFF);
	addr[4] = (u8) ((data32_lo >> 8) & 0xFF);
	addr[5] = (u8) ((data32_lo) & 0xFF);
	return 0;
}

static int mac_reset(struct cmac *mac)
{
	u32 data32;
	int mac_in_reset, time_out = 100;
	int idx = mac->instance->index;

	data32 = readl(mac->adapter->regs + MAC_REG_CSR(idx));
	writel(data32 | F_MAC_RESET,
	       mac->adapter->regs + MAC_REG_CSR(idx));

	do {
		data32 = readl(mac->adapter->regs + MAC_REG_CSR(idx));

		mac_in_reset = data32 & F_MAC_RESET;
		if (mac_in_reset)
			udelay(1);
	} while (mac_in_reset && --time_out);

	if (mac_in_reset) {
		CH_ERR("%s: MAC %d reset timed out\n",
		       mac->adapter->name, idx);
		return 2;
	}

	return 0;
}

static int mac_set_rx_mode(struct cmac *mac, struct t1_rx_mode *rm)
{
	u32 val;

	val = readl(mac->adapter->regs
			    + MAC_REG_CSR(mac->instance->index));
	val &= ~(F_MAC_PROMISC | F_MAC_MC_ENABLE);
	val |= V_MAC_PROMISC(t1_rx_mode_promisc(rm) != 0);
	val |= V_MAC_MC_ENABLE(t1_rx_mode_allmulti(rm) != 0);
	writel(val,
	       mac->adapter->regs + MAC_REG_CSR(mac->instance->index));

	return 0;
}

static int mac_set_speed_duplex_fc(struct cmac *mac, int speed, int duplex,
				   int fc)
{
	u32 data32;

	data32 = readl(mac->adapter->regs
			       + MAC_REG_CSR(mac->instance->index));
	data32 &= ~(F_MAC_HALF_DUPLEX | V_MAC_SPEED(M_MAC_SPEED) |
		V_INTERFACE(M_INTERFACE) | F_MAC_TX_PAUSE_ENABLE |
		F_MAC_RX_PAUSE_ENABLE);

	switch (speed) {
	case SPEED_10:
	case SPEED_100:
		data32 |= V_INTERFACE(MAC_CSR_INTERFACE_MII);
		data32 |= V_MAC_SPEED(speed == SPEED_10 ? 0 : 1);
		break;
	case SPEED_1000:
		data32 |= V_INTERFACE(MAC_CSR_INTERFACE_GMII);
		data32 |= V_MAC_SPEED(2);
		break;
	}

	if (duplex >= 0)
		data32 |= V_MAC_HALF_DUPLEX(duplex == DUPLEX_HALF);

	if (fc >= 0) {
		data32 |= V_MAC_RX_PAUSE_ENABLE((fc & PAUSE_RX) != 0);
		data32 |= V_MAC_TX_PAUSE_ENABLE((fc & PAUSE_TX) != 0);
	}

	writel(data32,
	       mac->adapter->regs + MAC_REG_CSR(mac->instance->index));
	return 0;
}

static int mac_enable(struct cmac *mac, int which)
{
	u32 val;

	val = readl(mac->adapter->regs
			    + MAC_REG_CSR(mac->instance->index));
	if (which & MAC_DIRECTION_RX)
		val |= F_MAC_RX_ENABLE;
	if (which & MAC_DIRECTION_TX)
		val |= F_MAC_TX_ENABLE;
	writel(val,
	       mac->adapter->regs + MAC_REG_CSR(mac->instance->index));
	return 0;
}

static int mac_disable(struct cmac *mac, int which)
{
	u32 val;

	val = readl(mac->adapter->regs
			    + MAC_REG_CSR(mac->instance->index));
	if (which & MAC_DIRECTION_RX)
		val &= ~F_MAC_RX_ENABLE;
	if (which & MAC_DIRECTION_TX)
		val &= ~F_MAC_TX_ENABLE;
	writel(val,
	       mac->adapter->regs + MAC_REG_CSR(mac->instance->index));
	return 0;
}

#if 0
static int mac_set_ifs(struct cmac *mac, u32 mode)
{
	t1_write_reg_4(mac->adapter,
		       MAC_REG_IFS(mac->instance->index),
		       mode);
	return 0;
}

static int mac_enable_isl(struct cmac *mac)
{
	u32 data32 = readl(mac->adapter->regs
				   + MAC_REG_CSR(mac->instance->index));
	data32 |= F_MAC_RX_ENABLE | F_MAC_TX_ENABLE;
	t1_write_reg_4(mac->adapter,
		       MAC_REG_CSR(mac->instance->index),
		       data32);
	return 0;
}
#endif

static int mac_set_mtu(struct cmac *mac, int mtu)
{
	if (mtu > 9600)
		return -EINVAL;
	writel(mtu + ETH_HLEN + VLAN_HLEN,
	       mac->adapter->regs + MAC_REG_LARGEFRAMELENGTH(mac->instance->index));

	return 0;
}

static const struct cmac_statistics *mac_update_statistics(struct cmac *mac,
							   int flag)
{
	struct mac_statistics st;
	u32 *p = (u32 *) & st, i;

	writel(0,
	       mac->adapter->regs + MAC_REG_RMCNT(mac->instance->index));

	for (i = 0; i < sizeof(st) / sizeof(u32); i++)
		*p++ = readl(mac->adapter->regs
			     + MAC_REG_RMDATA(mac->instance->index));

	/* XXX convert stats */
	return &mac->stats;
}

static void mac_destroy(struct cmac *mac)
{
	kfree(mac);
}

static struct cmac_ops chelsio_mac_ops = {
	.destroy                 = mac_destroy,
	.reset                   = mac_reset,
	.interrupt_enable        = mac_intr_enable,
	.interrupt_disable       = mac_intr_disable,
	.interrupt_clear         = mac_intr_clear,
	.enable                  = mac_enable,
	.disable                 = mac_disable,
	.set_mtu                 = mac_set_mtu,
	.set_rx_mode             = mac_set_rx_mode,
	.set_speed_duplex_fc     = mac_set_speed_duplex_fc,
	.macaddress_get          = mac_get_address,
	.statistics_update       = mac_update_statistics,
};

static struct cmac *mac_create(adapter_t *adapter, int index)
{
	struct cmac *mac;
	u32 data32;

	if (index >= 4)
		return NULL;

	mac = kzalloc(sizeof(*mac) + sizeof(cmac_instance), GFP_KERNEL);
	if (!mac)
		return NULL;

	mac->ops = &chelsio_mac_ops;
	mac->instance = (cmac_instance *) (mac + 1);

	mac->instance->index = index;
	mac->adapter = adapter;

	data32 = readl(adapter->regs + MAC_REG_CSR(mac->instance->index));
	data32 &= ~(F_MAC_RESET | F_MAC_PROMISC | F_MAC_PROMISC |
		    F_MAC_LB_ENABLE | F_MAC_RX_ENABLE | F_MAC_TX_ENABLE);
	data32 |= F_MAC_JUMBO_ENABLE;
	writel(data32, adapter->regs + MAC_REG_CSR(mac->instance->index));

	/* Initialize the random backoff seed. */
	data32 = 0x55aa + (3 * index);
	writel(data32,
	       adapter->regs + MAC_REG_GMRANDBACKOFFSEED(mac->instance->index));

	/* Check to see if the mac address needs to be set manually. */
	data32 = readl(adapter->regs + MAC_REG_IDLO(mac->instance->index));
	if (data32 == 0 || data32 == 0xffffffff) {
		/*
		 * Add a default MAC address if we can't read one.
		 */
		writel(0x43FFFFFF - index,
		       adapter->regs + MAC_REG_IDLO(mac->instance->index));
		writel(0x0007,
		       adapter->regs + MAC_REG_IDHI(mac->instance->index));
	}

	(void) mac_set_mtu(mac, 1500);
	return mac;
}

struct gmac t1_chelsio_mac_ops = {
	.create = mac_create
};
