#include <ppcboot.h>
#include <mpc824x.h>
#include <net.h>

#include "pci.h"

	/* Ethernet chip registers.
	 */
#define SCBStatus       	0 	   /* Rx/Command Unit Status *Word* */
#define SCBIntAckByte   	1 	   /* Rx/Command Unit STAT/ACK byte */
#define SCBCmd          	2 	   /* Rx/Command Unit Command *Word* */
#define SCBIntrCtlByte  	3 	   /* Rx/Command Unit Intr.Control Byte */
#define SCBPointer      	4 	   /* General purpose pointer. */
#define SCBPort         	8 	   /* Misc. commands and operands. */
#define SCBflash        	12	   /* Flash memory control. */
#define SCBeeprom       	14	   /* EEPROM memory control. */
#define SCBCtrlMDI      	16	   /* MDI interface control. */
#define SCBEarlyRx      	20	   /* Early receive byte count. */
#define SCBGenControl   	28	   /* 82559 General Control Register */
#define SCBGenStatus    	29	   /* 82559 General Status register */

	/* 82559 SCB status word defnitions
	 */
#define SCB_STATUS_CX   	0x8000     /* CU finished command (transmit) */
#define SCB_STATUS_FR   	0x4000     /* frame received */
#define SCB_STATUS_CNA  	0x2000     /* CU left active state */
#define SCB_STATUS_RNR  	0x1000     /* receiver left ready state */
#define SCB_STATUS_MDI  	0x0800     /* MDI read/write cycle done */
#define SCB_STATUS_SWI  	0x0400     /* software generated interrupt */
#define SCB_STATUS_FCP  	0x0100     /* flow control pause interrupt */

#define SCB_INTACK_MASK 	0xFD00     /* all the above */

#define SCB_INTACK_TX 		(SCB_STATUS_CX | SCB_STATUS_CNA)
#define SCB_INTACK_RX 		(SCB_STATUS_FR | SCB_STATUS_RNR)

	/* System control block commands
	 */
/* CU Commands */
#define CU_NOP          	0x0000
#define	CU_START        	0x0010
#define	CU_RESUME       	0x0020
#define	CU_STATSADDR    	0x0040     /* Load Dump Statistics ctrs addr */
#define	CU_SHOWSTATS    	0x0050     /* Dump statistics counters. */
#define	CU_ADDR_LOAD    	0x0060     /* Base address to add to CU commands */
#define	CU_DUMPSTATS    	0x0070     /* Dump then reset stats counters. */

/* RUC Commands */
#define RUC_NOP         	0x0000
#define	RUC_START       	0x0001
#define	RUC_RESUME      	0x0002
#define RUC_ABORT       	0x0004
#define	RUC_ADDR_LOAD   	0x0006     /* (seems not to clear on acceptance) */
#define RUC_RESUMENR    	0x0007

#define CU_CMD_MASK     	0x00f0
#define RU_CMD_MASK     	0x0007

#define SCB_M	        	0x0100     /* 0 = enable interrupt, 1 = disable */
#define SCB_SWI         	0x0200     /* 1 - cause device to interrupt */

#define CU_STATUS_MASK  	0x00C0
#define RU_STATUS_MASK  	0x003C

#define RU_STATUS_IDLE  	(0<<2)
#define RU_STATUS_SUS   	(1<<2)
#define RU_STATUS_NORES 	(2<<2)
#define RU_STATUS_READY 	(4<<2)
#define RU_STATUS_NO_RBDS_SUS   ((1<<2)|(8<<2))
#define RU_STATUS_NO_RBDS_NORES ((2<<2)|(8<<2))
#define RU_STATUS_NO_RBDS_READY ((4<<2)|(8<<2))

	/* 82559 Port interface commands.
	 */
#define I82559_RESET		0x00000000 /* Software reset */
#define I82559_SELFTEST		0x00000001 /* 82559 Selftest command */
#define I82559_SELECTIVE_RESET	0x00000002
#define I82559_DUMP		0x00000003
#define I82559_DUMP_WAKEUP	0x00000007

	/* 82559 Eeprom interface.
	 */
#define EE_SHIFT_CLK		0x01	   /* EEPROM shift clock. */
#define EE_CS			0x02	   /* EEPROM chip select. */
#define EE_DATA_WRITE		0x04	   /* EEPROM chip data in. */
#define EE_WRITE_0		0x01
#define EE_WRITE_1		0x05
#define EE_DATA_READ		0x08	   /* EEPROM chip data out. */
#define EE_ENB			(0x4800 | EE_CS)

	/* The EEPROM commands include the alway-set leading bit. 
	 */
#define EE_WRITE_CMD		(5 << addr_len)
#define EE_READ_CMD		(6 << addr_len)
#define EE_ERASE_CMD		(7 << addr_len)

	/* Receive frame descriptors.
	 */
struct RxFD {
	volatile u16 status;
	volatile u16 control;
	volatile u32 link;		    /* struct RxFD * */
	volatile u32 rx_buf_addr;	    /* void * */
	volatile u32 count;

	volatile u8 data[PKTSIZE_ALIGN];
};

#define RFD_STATUS_C		0x8000      /* completion of received frame */
#define RFD_STATUS_OK   	0x2000      /* frame received with no errors */

#define RFD_CONTROL_EL   	0x8000      /* 1=last RFD in RFA */
#define RFD_CONTROL_S    	0x4000      /* 1=suspend RU after receiving frame */
#define RFD_CONTROL_H    	0x0010      /* 1=RFD is a header RFD */
#define RFD_CONTROL_SF   	0x0008      /* 0=simplified, 1=flexible mode */

#define RFD_COUNT_MASK     	0x3fff
#define RFD_COUNT_F        	0x4000
#define RFD_COUNT_EOF      	0x8000

#define RFD_RX_CRC          	0x0800      /* crc error */
#define RFD_RX_ALIGNMENT    	0x0400      /* alignment error */
#define RFD_RX_RESOURCE     	0x0200      /* out of space, no resources */
#define RFD_RX_DMA_OVER     	0x0100      /* DMA overrun */
#define RFD_RX_SHORT        	0x0080      /* short frame error */
#define RFD_RX_LENGTH       	0x0020
#define RFD_RX_ERROR        	0x0010      /* receive error */
#define RFD_RX_NO_ADR_MATCH 	0x0004      /* no address match */
#define RFD_RX_IA_MATCH     	0x0002      /* individual address does not match */
#define RFD_RX_TCO          	0x0001      /* TCO indication */

	/* Transmit frame descriptors
	 */
struct TxFD {				    /* Transmit frame descriptor set. */
	volatile u16 status;
	volatile u16 command;
	volatile u32 link;		    /* void * */
	volatile u32 tx_desc_addr;	    /* Always points to the tx_buf_addr element. */
	volatile s32 count;

	volatile u32 tx_buf_addr0;	    /* void *, frame to be transmitted.  */
	volatile s32 tx_buf_size0;	    /* Length of Tx frame. */
	volatile u32 tx_buf_addr1;	    /* void *, frame to be transmitted.  */
	volatile s32 tx_buf_size1;	    /* Length of Tx frame. */
};

#define TxCB_CMD_TRANSMIT	0x0004      /* transmit command */
#define TxCB_CMD_SF         	0x0008      /* 0=simplified, 1=flexible mode */
#define TxCB_CMD_NC         	0x0010      /* 0=CRC insert by controller */
#define TxCB_CMD_I          	0x2000      /* generate interrupt on completion */
#define TxCB_CMD_S          	0x4000      /* suspend on completion */
#define TxCB_CMD_EL         	0x8000      /* last command block in CBL */

#define TxCB_COUNT_MASK     	0x3fff
#define TxCB_COUNT_EOF      	0x8000

	/* The Speedo3 Rx and Tx frame/buffer descriptors. 
	 */
struct descriptor {			    /* A generic descriptor. */
	volatile u16 status;
	volatile u16 command;
	volatile u32 link;		    /* struct descriptor *  */

	unsigned char params[0];
};

#define CFG_CMD_EL         	0x8000
#define CFG_CMD_SUSPEND    	0x4000
#define CFG_CMD_INT        	0x2000
#define CFG_CMD_IAS        	0x0001       /* individual address setup */
#define CFG_CMD_CONFIGURE  	0x0002       /* configure */

#define CFG_STATUS_C       	0x8000
#define CFG_STATUS_OK      	0x2000

	/* Misc.
	 */
#define NUM_RX_DESC 		PKTBUFSRX
#define NUM_TX_DESC 		1            /* Number of TX descriptors   */

#define TOUT_LOOP		1000000

#define ETH_ALEN		6

static struct RxFD rx_ring[NUM_RX_DESC]; /* RX descriptor ring         */
static struct TxFD tx_ring[NUM_TX_DESC]; /* TX descriptor ring         */
static int rx_next;                      /* RX descriptor ring pointer */
static int tx_next;                      /* TX descriptor ring pointer */
static int tx_threshold;

static u_long iobase = 0;

static void  init_rx_ring(void);
static void  purge_tx_ring(void);

static void  check_hw_addr(bd_t * bis);

static inline int INL(u_long addr)
{
	return le32_to_cpu(*(volatile u32 *)(addr + iobase));
}

static inline int INW(u_long addr)
{
	return le16_to_cpu(*(volatile u16 *)(addr + iobase));
}

static inline int INB(u_long addr)
{
	return *(volatile u8 *)(addr + iobase);
}

static inline void OUTB(int command, u_long addr)
{
	*(volatile u8 *)(addr + iobase) = command;
}

static inline void OUTW(int command, u_long addr)
{
	*(volatile u16 *)(addr + iobase) = cpu_to_le16(command);
}

static inline void OUTL(int command, u_long addr)
{
	*(volatile u32 *)(addr + iobase) = cpu_to_le32(command);
}

	/* Wait for the chip get the command.
	 */
static int wait_for_eepro100(void) 
{
	int i;

	for(i = 0; INW(SCBCmd) & (CU_CMD_MASK | RU_CMD_MASK); i++)
	{
		if (i >= TOUT_LOOP)
		{
			return 0;
		}
	}

	return 1;
}

int eth_init(bd_t *bis)
{
	int i, status = 0;
	int bus, devno, func;
	int tx_cur;
	struct descriptor *ias_cmd;

	/* Find PCI device 
	 */
	if (pci_dev_find(PCI_VENDOR_ID_INTEL,
			 PCI_DEVICE_ID_INTEL_82557,
			 0, &bus, &devno, &func) != TRUE)
	{
		printf("Error: Can not find an ethernet card on the PCI bus\n");
		goto Done;
	}

	pci_config_inl (bus, devno, func,
			PCI_CFG_BASE_ADDRESS_0, (uint32 *)&iobase);
	iobase &= PCI_MEMBASE_MASK;

	printf("eth: Intel i82559 PCI EtherExpressPro @0x%lX"
	       "(bus=%d, device=%d, func=%d)\n",
	       iobase, bus, devno, func);
  
	pci_config_outl (bus, devno, func, 
			 PCI_CFG_COMMAND, 
			 PCI_CMD_MEM_ENABLE | 
			 PCI_CMD_MASTER_ENABLE);

	 /* Check if I/O accesses and Bus Mastering are enabled.
	  */
	pci_config_inl (bus, devno, func,
			PCI_CFG_COMMAND, &status);

	if (!(status & PCI_CMD_MEM_ENABLE))
	{
		printf("Error: Can not enable MEM access.\n");
		goto Done;
	}

	if (!(status & PCI_CMD_MASTER_ENABLE))
	{
		printf("Error: Can not enable Bus Mastering.\n");
		goto Done;
	}

		/* Set the latency timer for value.
		 */
	pci_config_outb(bus, devno, func, 
			PCI_CFG_LATENCY_TIMER, 
			PCI_LATENCY_TIMER);

	udelay(10 * 1000);

	check_hw_addr(bis);

	/* Reset the ethernet controller
	 */
	OUTL(I82559_SELECTIVE_RESET, SCBPort);
	udelay(20);

	OUTL(I82559_RESET, SCBPort);
	udelay(20);

	if (!wait_for_eepro100())
	{
		printf("Error: Can not reset ethernet controller.\n");
		goto Done;
	}
	OUTL(0, SCBPointer);
	OUTW(SCB_M | RUC_ADDR_LOAD, SCBCmd);

	if (!wait_for_eepro100())
	{
		printf("Error: Can not reset ethernet controller.\n");
		goto Done;
	}
	OUTL(0, SCBPointer);
	OUTW(SCB_M | CU_ADDR_LOAD, SCBCmd);

		/* Initialize Rx and Tx rings.
		 */
	init_rx_ring();
	purge_tx_ring();

		/* Tell the adapter where the RX ring is located.
		 */
	if (!wait_for_eepro100())
	{
		printf("Error: Can not reset ethernet controller.\n");
		goto Done;
	}

	OUTL((u32) &rx_ring[rx_next], SCBPointer);
	OUTW(SCB_M | RUC_START, SCBCmd);

		/* Send the Individual Address Setup frame
		 */
	tx_cur       = tx_next;
	tx_next      = ((tx_next+1) % NUM_TX_DESC);

	ias_cmd             = (struct descriptor *)&tx_ring[tx_cur];
	ias_cmd->command    = cpu_to_le16((CFG_CMD_SUSPEND | CFG_CMD_IAS));
	ias_cmd->status     = 0;
	ias_cmd->link       = cpu_to_le32((u32) &tx_ring[tx_next]);

	memcpy(ias_cmd->params, bis->bi_enetaddr, 6);

		/* Tell the adapter where the TX ring is located.
		 */
	if (!wait_for_eepro100())
	{
		printf("Error: Can not reset ethernet controller.\n");
		goto Done;
	}

	OUTL((u32) &tx_ring[tx_cur], SCBPointer);
	OUTW(SCB_M | CU_START, SCBCmd);

	for (i=0; !(le16_to_cpu(tx_ring[tx_cur].status) & CFG_STATUS_C); i++)
	{
		if (i >= TOUT_LOOP)
		{
			printf("eth: Tx error buffer not ready\n");
			goto Done;
		}
	}

	if (!(le16_to_cpu(tx_ring[tx_cur].status) & CFG_STATUS_OK))
	{
		printf("TX error status = 0x%08X\n",
		       le16_to_cpu(tx_ring[tx_cur].status));
		status++;
	}

Done:
	return status;
}

int eth_send(volatile void *packet, int length)
{
	int i, status = 0;
	int tx_cur;

	if (length <= 0)
	{
		printf("eth: bad packet size: %d\n", length);
		goto Done;
	}

	tx_cur  = tx_next;
	tx_next = (tx_next+1) % NUM_TX_DESC;

	tx_ring[tx_cur].command      = cpu_to_le16(TxCB_CMD_TRANSMIT | TxCB_CMD_SF | \
						   TxCB_CMD_S | TxCB_CMD_EL);
	tx_ring[tx_cur].status       = 0;
	tx_ring[tx_cur].count        = cpu_to_le32(tx_threshold);
	tx_ring[tx_cur].link         = cpu_to_le32((u32) &tx_ring[tx_next]);
	tx_ring[tx_cur].tx_desc_addr = cpu_to_le32((u32) &tx_ring[tx_cur].tx_buf_addr0);
	tx_ring[tx_cur].tx_buf_addr0 = cpu_to_le32((u_long)packet);
	tx_ring[tx_cur].tx_buf_size0 = cpu_to_le32(length);

	if (!wait_for_eepro100())
	{
		printf("eth: Tx error ethernet controller not ready.\n");
		goto Done;
	}

	/* Send the packet.
	 */
	OUTL((u32) &tx_ring[tx_cur], SCBPointer);
	OUTW(SCB_M | CU_START, SCBCmd);

	for(i = 0; !(le16_to_cpu(tx_ring[tx_cur].status) & CFG_STATUS_C); i++)
	{
		if (i >= TOUT_LOOP)
		{
			printf("eth: Tx error buffer not ready\n");
			goto Done;
		}
	}

        if (!(le16_to_cpu(tx_ring[tx_cur].status) & CFG_STATUS_OK))
	{
		printf("TX error status = 0x%08X\n",
		       le16_to_cpu(tx_ring[tx_cur].status));
		status++;
	}

 Done:
	return status;
}

int eth_rx(void)
{
	u16 status, stat;
	int rx_prev, length = 0;

	stat = INW(SCBStatus);
	OUTW(stat & SCB_STATUS_RNR, SCBStatus);

	for ( ; ; )
	{
		status = le16_to_cpu(rx_ring[rx_next].status);

		if (!(status & RFD_STATUS_C))
		{
			break;
		}

			/* Valid frame status.
			 */
		if ((status & RFD_STATUS_OK))
		{
				/* A valid frame received.
				 */
			length = le32_to_cpu(rx_ring[rx_next].count) & 0x3fff;

				/* Pass the packet up to the protocol
				 * layers.
				 */
			NetReceive(rx_ring[rx_next].data, length);
		}
		else
		{
				/* There was an error.
				 */
			printf("RX error status = 0x%08X\n", status);
		}

		rx_ring[rx_next].control = cpu_to_le16(RFD_CONTROL_S);
		rx_ring[rx_next].status  = 0;
		rx_ring[rx_next].count   = cpu_to_le32(PKTSIZE_ALIGN << 16);

		rx_prev                  = (rx_next + NUM_RX_DESC - 1) % NUM_RX_DESC;
		rx_ring[rx_prev].control = 0;

		/* Update entry information.
		 */
		rx_next = (rx_next + 1) % NUM_RX_DESC;
	}

	if (stat & SCB_STATUS_RNR)
	{

		printf("eth: Receiver is not ready, restart it !\n");

		/* Reinitialize Rx ring.
		 */
		init_rx_ring();

		if (!wait_for_eepro100())
		{
			printf("Error: Can not restart ethernet controller.\n");
			goto Done;
		}

		OUTL((u32) &rx_ring[rx_next], SCBPointer);
		OUTW(SCB_M | RUC_START, SCBCmd);
	}

 Done:
	return length;
}

void eth_halt(void)
{
	if (!iobase) 
	{
		goto Done;
	}

	/* Reset the ethernet controller
	 */
	OUTL(I82559_SELECTIVE_RESET, SCBPort);
	udelay(20);

	OUTL(I82559_RESET, SCBPort);
	udelay(20);

	if (!wait_for_eepro100())
	{
		printf("Error: Can not reset ethernet controller.\n");
		goto Done;
	}
	OUTL(0, SCBPointer);
	OUTW(SCB_M | RUC_ADDR_LOAD, SCBCmd);

	if (!wait_for_eepro100())
	{
		printf("Error: Can not reset ethernet controller.\n");
		goto Done;
	}
	OUTL(0, SCBPointer);
	OUTW(SCB_M | CU_ADDR_LOAD, SCBCmd);

 Done:
	return;
}

	/* SROM Read.
	 */
static int read_eeprom(long iobase, int location, int addr_len)
{
	unsigned short retval = 0;
	int read_cmd = location | EE_READ_CMD;
	int i;

	OUTW(EE_ENB & ~EE_CS, SCBeeprom);
	OUTW(EE_ENB, SCBeeprom);

	/* Shift the read command bits out. */
	for (i = 12; i >= 0; i--) 
	{
		short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
		OUTW(EE_ENB | dataval, SCBeeprom);
		udelay(1);
		OUTW(EE_ENB | dataval | EE_SHIFT_CLK, SCBeeprom);
		udelay(1);
	}
	OUTW(EE_ENB, SCBeeprom);

	for (i = 15; i >= 0; i--) 
	{
		OUTW(EE_ENB | EE_SHIFT_CLK, SCBeeprom);
		udelay(1);
		retval = (retval << 1) | 
			((INW(SCBeeprom) & EE_DATA_READ) ? 1 : 0);
		OUTW(EE_ENB, SCBeeprom);
		udelay(1);
	}

	/* Terminate the EEPROM access. */
	OUTW(EE_ENB & ~EE_CS, SCBeeprom);
	return retval;
}

static void init_rx_ring(void) 
{
	int i;

	for (i = 0; i < NUM_RX_DESC; i++)
	{
		rx_ring[i].status      = 0;
		rx_ring[i].control     = (i == NUM_RX_DESC-1) ? cpu_to_le16(RFD_CONTROL_S) : 0;
		rx_ring[i].link        = cpu_to_le32((u32) &rx_ring[(i+1) % NUM_RX_DESC]);
		rx_ring[i].rx_buf_addr = 0xffffffff;
		rx_ring[i].count       = cpu_to_le32(PKTSIZE_ALIGN << 16);
	}

	rx_next = 0;
}

static void purge_tx_ring(void) 
{
	int i;

	tx_next      = 0;
	tx_threshold = 0x01208000;

	for (i = 0; i < NUM_TX_DESC; i++)
	{
		tx_ring[i].status       = 0;
		tx_ring[i].command      = 0;
		tx_ring[i].link         = 0;
		tx_ring[i].tx_desc_addr = 0;
		tx_ring[i].count        = 0;

		tx_ring[i].tx_buf_addr0 = 0;
		tx_ring[i].tx_buf_size0 = 0;
		tx_ring[i].tx_buf_addr1 = 0;
		tx_ring[i].tx_buf_size1 = 0;
	}
}

static void check_hw_addr(bd_t *bis) 
{
	u16 eeprom[0x40];
	u16 sum = 0;
	u8 hw_addr[ETH_ALEN];
	int i, j;
	int addr_len = read_eeprom(iobase, 0, 6) == 0xffff ? 8 : 6;

	for (j = 0, i = 0; i < 0x40; i++) 
	{
		u16 value = read_eeprom(iobase, i, addr_len);
		eeprom[i] = value;
		sum += value;
		if (i < 3) 
		{
			hw_addr[j++] = value;
			hw_addr[j++] = value >> 8;
		}
	}

	if (sum != 0xBABA)
		printf("eth: Invalid EEPROM checksum %#4.4x, "
		       "check settings before activating this device!\n",
		       sum);

	for (i=0;i<ETH_ALEN;i++) 
	{
		if (hw_addr[i] != bis->bi_enetaddr[i]) 
		{
			printf("Warning: HW address don't match:\n");
			printf("Address in SROM is         "
			       "%02X:%02X:%02X:%02X:%02X:%02X\n",
			       hw_addr[0], hw_addr[1], hw_addr[2],
			       hw_addr[3], hw_addr[4], hw_addr[5]);
			printf("Address used by ppcboot is "
			       "%02X:%02X:%02X:%02X:%02X:%02X\n",
			       bis->bi_enetaddr[0], bis->bi_enetaddr[1], 
			       bis->bi_enetaddr[2], bis->bi_enetaddr[3], 
			       bis->bi_enetaddr[4], bis->bi_enetaddr[5]);
			goto Done;
		}
	}

Done:
	return;
}
