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

	/* PCI Registers.
	 */
#define PCI_VENDOR_ID       0x00
#define PCI_COMMAND         0x04
#define PCI_CLASS_REVISION  0x08
#define PCI_LATENCY_TIMER   0x0d
#define PCI_BASE_ADDRESS_0  0x10
#define PCI_CFDA_PSM        0x43

#define PCI_COMMAND_IO      0x1
#define PCI_COMMAND_MASTER  0x4

#define CFRV_RN     0x000000f0       /* Revision Number */
#define CBIO_MASK   -128

#define WAKEUP      0x00             /* Power Saving Wakeup */
#define SLEEP       0x80             /* Power Saving Sleep Mode */


#define DC2114x_VID 0x1011           /* DC2114[23] Manufacturer */
#define DC2114x_DID 0x1900           /* Unique Device ID # */
#define DC2114x_BRK 0x0020           /* CFRV break between DC21142 & DC21143 */
#define DC21142     (DC2114x_DID | 0x0010)
#define DC21143     (DC2114x_DID | 0x0030)

#define is_DC2114x ((vendor == DC2114x_VID) && (device == DC2114x_DID))

	/* Ethernet chip registers.
	 */
#define DE4X5_BMR    iobase + 0x000  /* Bus Mode Register */
#define DE4X5_TPD    iobase + 0x008  /* Transmit Poll Demand Reg */
#define DE4X5_RRBA   iobase + 0x018  /* RX Ring Base Address Reg */
#define DE4X5_TRBA   iobase + 0x020  /* TX Ring Base Address Reg */
#define DE4X5_STS    iobase + 0x028  /* Status Register */
#define DE4X5_OMR    iobase + 0x030  /* Operation Mode Register */
#define DE4X5_SICR   iobase + 0x068  /* SIA Connectivity Register */
#define DE4X5_APROM  iobase + 0x048  /* Ethernet Address PROM */

	/* Register bits.
	 */
#define BMR_SWR    0x00000001       /* Software Reset */
#define STS_TS     0x00700000       /* Transmit Process State */
#define STS_RS     0x000e0000       /* Receive Process State */
#define OMR_ST     0x00002000       /* Start/Stop Transmission Command */
#define OMR_SR     0x00000002       /* Start/Stop Receive */
#define OMR_PS     0x00040000       /* Port Select */
#define OMR_SDP    0x02000000       /* SD Polarity - MUST BE ASSERTED */
#define OMR_PM     0x00000080       /* Pass All Multicast */

	/* Descriptor bits.
	 */
#define R_OWN      0x80000000       /* Own Bit */
#define RD_RER     0x02000000       /* Receive End Of Ring */
#define RD_LS      0x00000100       /* Last Descriptor */
#define RD_ES      0x00008000       /* Error Summary */
#define TD_TER     0x02000000       /* Transmit End Of Ring */
#define T_OWN      0x80000000       /* Own Bit */
#define TD_LS      0x40000000       /* Last Segment */
#define TD_FS      0x20000000       /* First Segment */
#define TD_ES      0x00008000       /* Error Summary */
#define TD_SET     0x08000000       /* Setup Packet */


#define SROM_HWADD     0x0014       /* Hardware Address offset in SROM */
#define SROM_RD    0x00004000       /* Read from Boot ROM */
#define SROM_SR    0x00000800       /* Select Serial ROM when set */

#define DT_IN      0x00000004       /* Serial Data In */
#define DT_CLK     0x00000002       /* Serial ROM Clock */
#define DT_CS      0x00000001       /* Serial ROM Chip Select */

#define POLL_DEMAND          1

#define RESET_DE4X5 {\
    int i;\
    i=inl(DE4X5_BMR);\
    udelay(1000);\
    outl(i | BMR_SWR, DE4X5_BMR);\
    udelay(1000);\
    outl(i, DE4X5_BMR);\
    udelay(1000);\
    for (i=0;i<5;i++) {inl(DE4X5_BMR); udelay(10000);}\
    udelay(1000);\
}

#define START_DE4X5 {\
    s32 omr; \
    omr = inl(DE4X5_OMR);\
    omr |= OMR_ST | OMR_SR;\
    outl(omr, DE4X5_OMR);               /* Enable the TX and/or RX */\
}

#define STOP_DE4X5 {\
    s32 omr; \
    omr = inl(DE4X5_OMR);\
    omr &= ~(OMR_ST|OMR_SR);\
    outl(omr, DE4X5_OMR);               /* Disable the TX and/or RX */ \
}

#define NUM_RX_DESC PKTBUFSRX
#define NUM_TX_DESC 1                   /* Number of TX descriptors   */
#define RX_BUFF_SZ  PKTSIZE_ALIGN

#define TOUT_LOOP   1000000

#define SETUP_FRAME_LEN 192
#define ETH_ALEN        6


struct de4x5_desc {
    volatile s32 status;
    u32 des1;
    u32 buf;
    u32 next;
};


static struct de4x5_desc rx_ring[NUM_RX_DESC]; /* RX descriptor ring         */
static struct de4x5_desc tx_ring[NUM_TX_DESC]; /* TX descriptor ring         */
static int rx_new;                             /* RX descriptor ring pointer */
static int tx_new;                             /* TX descriptor ring pointer */

static char rxRingSize;
static char txRingSize;

static u_long iobase;

static void  send_setup_frame(bd_t * bis);
static void  check_hw_addr(bd_t * bis);
static short srom_rd(u_long address, u_char offset);
static void  srom_latch(u_int command, u_long address);
static void  srom_command(u_int command, u_long address);
static void  srom_address(u_int command, u_long address, u_char offset);
static short srom_data(u_int command, u_long address);
static void  sendto_srom(u_int command, u_long addr);
static int   getfrom_srom(u_long addr);

static inline int inl(u_long addr)
{
	return le32_to_cpu(*(volatile u_long *)(addr + 0xfe000000));
}

static inline void outl (int command, u_long addr)
{
	*(volatile u_long *)(addr + 0xfe000000) = cpu_to_le32(command);
}

int eth_init(bd_t *bis)
{
	int             i, status          = 0;
	int             vendor, device, l;
	int             cfrv;
	unsigned char   timer;


	CONFIG_READ_WORD(0x80000000 | PCI_CLASS_REVISION | CFG_ETH_DEV_FN, l);

	l >>= 8;

	if (l != 0x00020000)
	{
		printf("Error: Can not find an ethernet card on the PCI bus %d "
		       "in slot %d\n", CFG_ETH_DEV_FN >> 16,
		                      (CFG_ETH_DEV_FN & 0xFFFF) >> 11);
		goto Done;
	}

	CONFIG_READ_WORD(0x80000000 | PCI_VENDOR_ID | CFG_ETH_DEV_FN, vendor);

	device = (vendor >> 16) & 0xffff;
	vendor = vendor & 0xffff;
	device = device << 8;

	if (! is_DC2114x)
	{
		printf("Error: The chip is not DC2114x.\n");
		goto Done;
	}

		/* Get the chip configuration revision register.
		 */
	CONFIG_READ_WORD(0x80000000 | PCI_CLASS_REVISION | CFG_ETH_DEV_FN,
	                 cfrv);

	device = ((cfrv & CFRV_RN) < DC2114x_BRK ? DC21142 : DC21143);

	if (device != DC21143)
	{
		printf("Error: The chip is not DC21143.\n");
		goto Done;
	}

		/* Check if I/O accesses and Bus Mastering are enabled.
		 */
	CONFIG_READ_HALFWORD(0x80000000 | PCI_COMMAND | CFG_ETH_DEV_FN, status);

	if (!(status & PCI_COMMAND_IO))
	{
		status |= PCI_COMMAND_IO;
		CONFIG_WRITE_HALFWORD(0x80000000 | PCI_COMMAND | CFG_ETH_DEV_FN,
		                      status);
		CONFIG_READ_HALFWORD( 0x80000000 | PCI_COMMAND | CFG_ETH_DEV_FN,
		                      status);
	}

	if (!(status & PCI_COMMAND_IO))
	{
		printf("Error: Can not enable I/O access.\n");
		goto Done;
	}

	if (!(status & PCI_COMMAND_MASTER))
	{
		status |= PCI_COMMAND_MASTER;
		CONFIG_WRITE_HALFWORD(0x80000000 | PCI_COMMAND | CFG_ETH_DEV_FN,
		                      status);
		CONFIG_READ_HALFWORD( 0x80000000 | PCI_COMMAND | CFG_ETH_DEV_FN,
		                      status);
	}
	if (!(status & PCI_COMMAND_MASTER))
	{
		printf("Error: Can not enable Bus Mastering.\n");
		goto Done;
	}

		/* Check the latency timer for values >= 0x60.
		 */
	CONFIG_READ_BYTE(0x80000000 | PCI_LATENCY_TIMER | CFG_ETH_DEV_FN,
	                 timer);
	if (timer < 0x60)
	{
		CONFIG_WRITE_BYTE(0x80000000 | PCI_LATENCY_TIMER |
		                               CFG_ETH_DEV_FN,
		                  0X60);
	}

		/* Set I/O base register.
		 */
	CONFIG_WRITE_WORD(0x80000000 | PCI_BASE_ADDRESS_0 | CFG_ETH_DEV_FN,
	                  CFG_ETH_IOBASE);

	CONFIG_READ_WORD(0x80000000 | PCI_BASE_ADDRESS_0 | CFG_ETH_DEV_FN,
	                 iobase);

	if (iobase == 0xffffffff)
	{
		printf("Error: Can not set I/O base register.\n");
		goto Done;
	}

	iobase &= CBIO_MASK;

		/* Ensure we're not sleeping.
		 */
	CONFIG_WRITE_BYTE(0x80000000 | CFG_ETH_DEV_FN | PCI_CFDA_PSM, WAKEUP);

	udelay(10 * 1000);

	check_hw_addr(bis);

	RESET_DE4X5;

	if ((inl(DE4X5_STS) & (STS_TS | STS_RS)) != 0)
	{
		printf("Error: Can not reset ethernet controller.\n");
		goto Done;
	}
	
	outl(OMR_SDP | OMR_PS | OMR_PM, DE4X5_OMR);

	for (i = 0; i < NUM_RX_DESC; i++)
	{
		rx_ring[i].status = cpu_to_le32(R_OWN);
		rx_ring[i].des1 = cpu_to_le32(RX_BUFF_SZ);
		rx_ring[i].buf = cpu_to_le32((u_long)NetRxPackets[i]);
		rx_ring[i].next = 0;
	}

	for (i=0; i < NUM_TX_DESC; i++)
	{
		tx_ring[i].status = 0;
		tx_ring[i].des1 = 0;
		tx_ring[i].buf = 0;
		tx_ring[i].next = 0;
	}

	rxRingSize = NUM_RX_DESC;
	txRingSize = NUM_TX_DESC;

		/* Write the end of list marker to the descriptor lists.
		 */
	rx_ring[rxRingSize - 1].des1 |= cpu_to_le32(RD_RER);
	tx_ring[txRingSize - 1].des1 |= cpu_to_le32(TD_TER);

		/* Tell the adapter where the TX/RX rings are located.
		 */
	outl((u_long)&rx_ring, DE4X5_RRBA);
	outl((u_long)&tx_ring, DE4X5_TRBA);

	START_DE4X5;

	tx_new = 0;
	rx_new = 0;

	send_setup_frame(bis);

Done:

	return 0;
}

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

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

	for(i = 0; tx_ring[tx_new].status & cpu_to_le32(T_OWN); i++)
	{
		if (i >= TOUT_LOOP)
		{
			printf("eth: tx error buffer not ready\n");
			goto out;
		}
	}

	tx_ring[tx_new].buf = cpu_to_le32((u_long)packet);
	tx_ring[tx_new].des1 = cpu_to_le32(TD_TER | TD_LS | TD_FS | length);
	tx_ring[tx_new].status = cpu_to_le32(T_OWN);

	outl(POLL_DEMAND, DE4X5_TPD);

	for(i = 0; tx_ring[tx_new].status & cpu_to_le32(T_OWN); i++)
	{
		if (i >= TOUT_LOOP)
		{
			printf("eth: tx buffer not ready\n");
			goto out;
		}
	}

	if (le32_to_cpu(tx_ring[tx_new].status) & TD_ES)
	{
#if 0
		printf("TX error status = 0x%08X\n",
		       le32_to_cpu(tx_ring[tx_new].status));
#endif
		status++;
	}

	out:
	return status;
}

int eth_rx(void)
{
	s32 status;
	int length    = 0;

	for ( ; ; )
	{
		status = (s32)le32_to_cpu(rx_ring[rx_new].status);

		if (status & R_OWN)
		{
			break;
		}

		if (status & RD_LS)
		{
				/* Valid frame status.
				 */
			if (status & RD_ES)
			{
					/* There was an error.
					 */
				printf("RX error status = 0x%08X\n", status);
			}
			else
			{
					/* A valid frame received.
					 */
				length = (le32_to_cpu(rx_ring[rx_new].status) >>
				                                          16);

					/* Pass the packet up to the protocol
					 * layers.
					 */
				NetReceive(NetRxPackets[rx_new], length - 4);
			}

				/* Change buffer ownership for this frame, back
				 * to the adapter.
				 */
			rx_ring[rx_new].status = cpu_to_le32(R_OWN);
		}

			/* Update entry information.
			 */
		rx_new = (rx_new + 1) % rxRingSize;
	}

  return length;
}

void eth_halt(void)
{
	if (iobase != CFG_ETH_IOBASE) {
		/* Ethernet has not been initialized yet.  */
		return;
	}

	STOP_DE4X5;
	outl(0, DE4X5_SICR);
	CONFIG_WRITE_BYTE(0x80000000 | CFG_ETH_DEV_FN | PCI_CFDA_PSM, SLEEP);
}

static void check_hw_addr(bd_t *bis)
{
	unsigned char hw_addr[ETH_ALEN];
	u_short tmp, *p = (short *)(&hw_addr[0]);
	int i, j = 0;

	for (i = 0; i < (ETH_ALEN >> 1); i++)
	{
		tmp = srom_rd(DE4X5_APROM, (SROM_HWADD >> 1) + i);
		*p = le16_to_cpu(tmp);
		j += *p++;
	}

	if ((j == 0) || (j == 0x2fffd))
	{
		printf("Warning: can't read HW address from SROM.\n");
		goto Done;
	}

	for (i = 0; i < ETH_ALEN; i++)
	{
		if (hw_addr[i] != bis->bi_enetaddr[i])
		{
			printf("Warning: HW addresses 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;
}

static void send_setup_frame(bd_t *bis)
{
	int    i;
	char   setup_frame[SETUP_FRAME_LEN];
	char * pa                            = &setup_frame[0];

	memset(pa, 0xff, SETUP_FRAME_LEN);

	for (i = 0; i < ETH_ALEN; i++)
	{
		*(pa + (i & 1)) = bis->bi_enetaddr[i];
		if (i & 0x01)
		{
			pa += 4;
		}
	}

	for(i = 0; tx_ring[tx_new].status & cpu_to_le32(T_OWN); i++)
	{
		if (i >= TOUT_LOOP)
		{
			printf("eth: tx error buffer not ready\n");
			goto out;
		}
	}
	
	tx_ring[tx_new].buf = cpu_to_le32((u_long)&setup_frame[0]);
	tx_ring[tx_new].des1 = cpu_to_le32(TD_TER | TD_SET| SETUP_FRAME_LEN);
	tx_ring[tx_new].status = cpu_to_le32(T_OWN);

	outl(POLL_DEMAND, DE4X5_TPD);

	for(i = 0; tx_ring[tx_new].status & cpu_to_le32(T_OWN); i++)
	{
		if (i >= TOUT_LOOP)
		{
			printf("eth: tx buffer not ready\n");
			goto out;
		}
	}

#if 0
	if (le32_to_cpu(tx_ring[tx_new].status) != 0x7FFFFFFF)
	{
		printf("TX error status = 0x%08X\n",
		       le32_to_cpu(tx_ring[tx_new].status));
	}
#endif
out:
	return;
}

	/* SROM Read.
	 */
static short
srom_rd(u_long addr, u_char offset)
{
    sendto_srom(SROM_RD | SROM_SR, addr);
    
    srom_latch(SROM_RD | SROM_SR | DT_CS, addr);
    srom_command(SROM_RD | SROM_SR | DT_IN | DT_CS, addr);
    srom_address(SROM_RD | SROM_SR | DT_CS, addr, offset);
    
    return srom_data(SROM_RD | SROM_SR | DT_CS, addr);
}

static void
srom_latch(u_int command, u_long addr)
{
    sendto_srom(command, addr);
    sendto_srom(command | DT_CLK, addr);
    sendto_srom(command, addr);
    
    return;
}

static void
srom_command(u_int command, u_long addr)
{
    srom_latch(command, addr);
    srom_latch(command, addr);
    srom_latch((command & 0x0000ff00) | DT_CS, addr);
    
    return;
}

static void
srom_address(u_int command, u_long addr, u_char offset)
{
    int i;
    signed char a;
    
    a = (char)(offset << 2);
    for (i=0; i<6; i++, a <<= 1) {
        srom_latch(command | ((a < 0) ? DT_IN : 0), addr);
    }
    udelay(1);
    
    i = (getfrom_srom(addr) >> 3) & 0x01;
    
    return;
}

static short
srom_data(u_int command, u_long addr)
{
    int i;
    short word = 0;
    s32 tmp;
    
    for (i=0; i<16; i++) {
        sendto_srom(command  | DT_CLK, addr);
        tmp = getfrom_srom(addr);
        sendto_srom(command, addr);
	
        word = (word << 1) | ((tmp >> 3) & 0x01);
    }
    
    sendto_srom(command & 0x0000ff00, addr);
    
    return word;
}

static void
sendto_srom(u_int command, u_long addr)
{
    outl(command, addr);
    udelay(1);
    
    return;
}

static int
getfrom_srom(u_long addr)
{
    s32 tmp;
    
    tmp = inl(addr);
    udelay(1);
    
    return tmp;
}

