/*
 * (C) Copyright 2001
 * Stefan Roese, esd gmbh germany, stefan.roese@esd-electronics.com
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

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


/* phy seed setup */
#define AUTO			99
#define _100BASET		100
#define _10BASET		10
#define HALF			22
#define FULL			44

/* phy register offsets */
#define PHY_BMCR		0x00
#define PHY_BMSR		0x01
#define PHY_PHY1DR1		0x02
#define PHY_PHYIDR2		0x03
#define PHY_ANAR		0x04
#define PHY_ANLPAR		0x05
#define PHY_ANER		0x06
#define PHY_ANNPTR		0x07
#define PHY_PHYSTS		0x10
#define PHY_MIPSCR		0x11
#define PHY_MIPGSR		0x12
#define PHY_DCR			0x13
#define PHY_FCSCR		0x14
#define PHY_RECR		0x15
#define PHY_PCSR		0x16
#define PHY_LBR			0x17
#define PHY_10BTSCR		0x18
#define PHY_PHYCTRL		0x19

/*phy ANLPAR */
#define PHY_ANLPAR_NP		0x8000
#define PHY_ANLPAR_ACK		0x4000
#define PHY_ANLPAR_RF		0x2000
#define PHY_ANLPAR_T4		0x0200
#define PHY_ANLPAR_TXFD		0x0100
#define PHY_ANLPAR_TX		0x0080
#define PHY_ANLPAR_10FD		0x0040
#define PHY_ANLPAR_10		0x0020
#define PHY_ANLPAR_100		0x0380	/* we can run at 100 */

/* 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_BASE_ADDRESS_1	0x14
#define PCI_BASE_ADDRESS_2	0x18
#define PCI_CFDA_PSM		0x43

#define PCI_COMMAND_IO		0x1
#define PCI_COMMAND_MEM		0x2
#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;

unsigned int     PCI_Read_CFG_Reg(int BusDevFunc, int Reg, int Width);
int    PCI_Write_CFG_Reg(int BusDevFunc, int Reg, unsigned int Value, int Width);

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 void updateSRom(bd_t *bis);

static unsigned long dcCsrRead(unsigned long, int);


static int inl(u_long addr)
{
  return le32_to_cpu(*(volatile u_long *)(addr));
}

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


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

#if 0 // test-only
  {
    int val;

    for (l=0; l<6; l++)
      {
        printf("%02x: ", l*0x10);
        for (i=0; i<4; i++)
          {
            val = PCI_Read_CFG_Reg(CFG_ETH_DEV_FN, l*16+i*4, 4);
            printf("%08x ", val);
          }
        printf("\n");
      }
    printf("\n");
  }
#endif

  l = PCI_Read_CFG_Reg(CFG_ETH_DEV_FN, PCI_CLASS_REVISION, 4);

  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;
    }

  vendor = PCI_Read_CFG_Reg(CFG_ETH_DEV_FN, PCI_VENDOR_ID, 4);

  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.
   */
  cfrv = PCI_Read_CFG_Reg(CFG_ETH_DEV_FN, PCI_CLASS_REVISION, 4);

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

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

  status = PCI_Read_CFG_Reg(CFG_ETH_DEV_FN, PCI_COMMAND, 2);
  status |= PCI_COMMAND_MASTER | PCI_COMMAND_IO | PCI_COMMAND_MEM;
  PCI_Write_CFG_Reg(CFG_ETH_DEV_FN, PCI_COMMAND, status, 2);

  /* Check the latency timer for values >= 0x60.
   */
  timer = PCI_Read_CFG_Reg(CFG_ETH_DEV_FN, PCI_LATENCY_TIMER, 1);
  if (timer < 0x60)
    {
      PCI_Write_CFG_Reg(CFG_ETH_DEV_FN, PCI_LATENCY_TIMER, 0x60, 1);
    }

  /* Set I/O base register.
   */
  PCI_Write_CFG_Reg(CFG_ETH_DEV_FN, PCI_BASE_ADDRESS_0, CFG_ETH_IOBASE, 4);
  iobase = PCI_Read_CFG_Reg(CFG_ETH_DEV_FN, PCI_BASE_ADDRESS_0, 4);

  PCI_Write_CFG_Reg(CFG_ETH_DEV_FN, PCI_BASE_ADDRESS_1, CFG_ETH_IOBASE, 4);
  iobase = PCI_Read_CFG_Reg(CFG_ETH_DEV_FN, PCI_BASE_ADDRESS_1, 4);

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

  iobase &= CBIO_MASK;
  iobase += 0xc0000000; // test-only

  /* Ensure we're not sleeping.
   */
  PCI_Write_CFG_Reg(CFG_ETH_DEV_FN, PCI_CFDA_PSM, WAKEUP, 1);

  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;

  bmcr = dcCsrRead(0, PHY_ANLPAR);
  printf("ENET Speed is %d Mbps - %s duplex connection\n",
         ((bmcr & PHY_ANLPAR_100) != 0) ? 100 : 10,
         ((bmcr & (PHY_ANLPAR_10FD | PHY_ANLPAR_TXFD)) != 0) ? "FULL" : "HALF");

  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 0 // test-only
  if (le32_to_cpu(tx_ring[tx_new].status) & TD_ES)
    {
      printf("TX error status = 0x%08X\n",
             le32_to_cpu(tx_ring[tx_new].status));
      status++;
    }
#endif

 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 1
	if (iobase != CFG_ETH_IOBASE) {
		/* Ethernet has not been initialized yet.  */
		return;
	}

	STOP_DE4X5;
	outl(0, DE4X5_SICR);
	PCI_Write_CFG_Reg(CFG_ETH_DEV_FN, PCI_CFDA_PSM, SLEEP, 1);
#endif
}

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");
      if ((bis->bi_enetaddr[0] != 0) || (bis->bi_enetaddr[1] != 0) ||
          (bis->bi_enetaddr[2] != 0) || (bis->bi_enetaddr[3] != 0) ||
          (bis->bi_enetaddr[4] != 0) || (bis->bi_enetaddr[5] != 0))
        {
          printf("Updating SROM data - please wait...\n");
          updateSRom(bis);
        }
      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]);
          if ((bis->bi_enetaddr[0] != 0) || (bis->bi_enetaddr[1] != 0) ||
              (bis->bi_enetaddr[2] != 0) || (bis->bi_enetaddr[3] != 0) ||
              (bis->bi_enetaddr[4] != 0) || (bis->bi_enetaddr[5] != 0))
            {
              printf("Updating SROM data - please wait...\n");
              updateSRom(bis);
            }
          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 (le32_to_cpu(tx_ring[tx_new].status) != 0x7FFFFFFF)
    {
      printf("TX error status2 = 0x%08X\n",
             le32_to_cpu(tx_ring[tx_new].status));
    }
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;
}

/***************************************************************************
 *
 * Routines for srom write access
 *
 */

#define DECPCI_REG_OFFSET		0x08	/* quad word aligned */
#define LONGSWAP(data)  ((unsigned long)                                  \
			  (((unsigned long)(data) >> 24)                 | \
			   ((unsigned long)(data)  << 24)                | \
			   (((unsigned long)(data) >> 8) & 0x0000ff00 )  | \
			   (((unsigned long)(data) << 8) & 0x00ff0000 )))
#define PCISWAP(x)	LONGSWAP(x)		/* processor big endian */

/*******************************************************************************
*
* dcCsrWrite - select and write a CSR register
*
*/
static void dcCsrWrite
(
 unsigned long 	devAdrs,		/* device address base */
 int 	reg,			/* register to select */
 unsigned long 	value			/* value to write */
 )
{
  unsigned long *	csrReg;

  csrReg = (unsigned long *)(devAdrs + (reg * DECPCI_REG_OFFSET));

  /* write val to CSR */

  *(csrReg) = PCISWAP(value);
}

/*******************************************************************************
*
* dcCsrRead - select and read a CSR register
*
*/
static unsigned long dcCsrRead
(
 unsigned long	devAdrs,		/* device address base */
 int		reg			/* register to select */
 )
{
  unsigned long *	csrReg;			/* csr register */
  unsigned long	csrData;		/* data in csr register */

  csrReg = (unsigned long *)(devAdrs + (reg * DECPCI_REG_OFFSET));

  csrData = *csrReg;

  /* get contents of CSR */

  return (PCISWAP(csrData));
}

/* define CSRs and descriptors */

#define CSR0	0		/* csr 0 */
#define CSR1	1		/* csr 1 */
#define CSR2	2		/* csr 2 */
#define CSR3	3		/* csr 3 */
#define CSR4	4		/* csr 4 */
#define CSR5	5		/* csr 5 */
#define CSR6	6		/* csr 6 */
#define CSR7	7		/* csr 7 */
#define CSR8	8		/* csr 8 */
#define CSR9	9		/* csr 9 */
#define CSR10	10		/* csr 10 */
#define CSR11	11		/* csr 11 */
#define CSR12	12		/* csr 12 */
#define CSR13	13		/* csr 13 */
#define CSR14	14		/* csr 14 */
#define CSR15	15		/* csr 15 */

/* CSR9 Ethernet Address ROM Register */

#define	CSR9_DNV	0x80000000	/* Data not valid */
#define CSR9_DAT_MSK	0x000000FF	/* data mask */
#define ENET_ROM_SIZE	8		/* ethernet rom register size */


/* CSR9 Serial Address ROM and MII Management Register for the DEC21140 */

#define	CSR9_040_DNVAL	0x80000000	/* Data not valid - 21040 */

#define	CSR9_MDI	0x00080000	/* MII mgmt data in - 21140+ */
#define	CSR9_MDI_SHF	19
#define	CSR9_MII_RD	0x00040000	/* MII mgmt read mode - 21140+ */
#define	CSR9_MII_WR	0x00000000	/* MII mgmt write mode - 21140+ */
#define	CSR9_MDO	0x00020000	/* MII mgmt write data - 21140+ */
#define	CSR9_MDO_SHF	17
#define	CSR9_MDC	0x00010000	/* MII mgmt clock - 21140+ */
#define	CSR9_RD		0x00004000	/* Read command - 21140+ */
#define	CSR9_WR		0x00002000	/* Write command - 21140+ */
#define	CSR9_BR		0x00001000	/* Boot rom select - 21140+ */
#define	CSR9_SR		0x00000800	/* Serial rom select - 21140+ */
#define	CSR9_REG	0x00000400	/* External register select - 21140+ */
#define CSR9_DATA	0x000000FF	/* Data */
#define CSR9_DATA_OUT	0x00000008	/* Shift read data from SROM - 21140+ */
#define	CSR9_DATA_IN	0x00000004	/* Shift write data into SROM - 21140+*/
#define CSR9_SROM_CLK	0x00000002	/* SCLK output to SROM - 21140+ */
#define	CSR9_SROM_CS	0x00000001	/* SerialROM chip select - 21140+ */

#define	CSR9_MII_DBIT_RD(X)	(((X) & CSR9_MDI) >> CSR9_MDI_SHF)
#define CSR9_MII_DBIT_WR(X)	(((X) & 0x1) << CSR9_MDO_SHF)

#define NSDELAY(nsec)	{                                               \
			volatile int nx = 0;                            \
			volatile int loop = (int)(nsec);                \
			for (nx = 0; nx < loop; nx++);                  \
			}

#ifndef DC_CSR_READ
#define DC_CSR_READ(devAdrs, csrNum)                                    \
    	dcCsrRead ((devAdrs), (csrNum))
#endif /* DC_CSR_READ */

#ifndef DC_CSR_WRITE
#define DC_CSR_WRITE(devAdrs, csrNum, csrVal)                           \
    	dcCsrWrite((devAdrs), (csrNum), (csrVal))
#endif /* DC_CSR_WRITE */

#define DC_CSR_UPDATE(devAdrs, csrNum, csrBits)                         \
    	DC_CSR_WRITE((devAdrs), (csrNum),                               \
                     DC_CSR_READ((devAdrs), (csrNum)) | (csrBits))

#define	DC_CSR_RESET(devAdrs, csrNum, csrBits)                          \
    	DC_CSR_WRITE((devAdrs), (csrNum),                               \
                     DC_CSR_READ((devAdrs), (csrNum)) & ~(csrBits))

#define DC_INT_ENABLE(devAdrs, X)                                       \
	DC_CSR_UPDATE ((devAdrs), CSR7, (X))

#define DC_INT_DISABLE(devAdrs, X)                                      \
    	DC_CSR_RESET ((devAdrs), CSR7, (X))

#define	DC_SROM_WRITE(devAdrs, adrs, delay)                             \
    	{                                                               \
        DC_CSR_WRITE ((devAdrs), CSR9, (adrs) | CSR9_SR | CSR9_WR);     \
        NSDELAY(delay);                                                 \
        }

#define	DC_SROM_READ(devAdrs)                                           \
	((DC_CSR_READ ((devAdrs), CSR9) & 0x08) >> 3)

/* Reading a serial EEPROM is a "bit" grungy, but we work our way through:->.*/
/* This code is a "nasty" timing loop, but PC compatible machines are
 *supposed* to delay an ISA-compatible period for the SLOW_DOWN_IO macro.  */
#define eeprom_delay()	  NSDELAY(100)

#undef inl
#undef outl
#define outl(val, addr)   DC_SROM_WRITE(addr, val, 0)
#define inl(addr)         DC_SROM_READ(addr)

#undef debug
#define debug             0


/*  EEPROM_Ctrl bits. */
#define EE_SHIFT_CLK	0x02	/* EEPROM shift clock. */
#define EE_CS			0x01	/* EEPROM chip select. */
#define EE_DATA_WRITE	0x04	/* EEPROM chip data in. */
#define EE_WRITE_0		0x4801
#define EE_WRITE_1		0x4805
#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)
#define EE_READ_CMD		(6)
#define EE_ERASE_CMD	(7)

/* Note: this routine returns extra data bits for size detection. */
static int read_eeprom(long ioaddr, int location, int addr_len)
{
  int i;
  unsigned retval = 0;
  long ee_addr = ioaddr;
  int read_cmd = location | (EE_READ_CMD << addr_len);

  outl(EE_ENB & ~EE_CS, ee_addr);
  outl(EE_ENB, ee_addr);

  if (debug > 2)
    printf(" EEPROM read at %d ", location);

  /* Shift the read command bits out. */
  for (i = 4 + addr_len; i >= 0; i--) {
    short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
    outl(EE_ENB | dataval, ee_addr);
    eeprom_delay();
    outl(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
    eeprom_delay();
    if (debug > 2)
      printf("%lX", inl(ee_addr) & 15);
    retval = (retval << 1) | ((inl(ee_addr) & EE_DATA_READ) ? 1 : 0);
  }
  outl(EE_ENB, ee_addr);
  if (debug > 2)
    printf(" :%lX:", inl(ee_addr) & 15);

  for (i = 16; i > 0; i--) {
    outl(EE_ENB | EE_SHIFT_CLK, ee_addr);
    eeprom_delay();
    if (debug > 2)
      printf("%lX", inl(ee_addr) & 15);
    retval = (retval << 1) | ((inl(ee_addr) & EE_DATA_READ) ? 1 : 0);
    outl(EE_ENB, ee_addr);
    eeprom_delay();
  }

  /* Terminate the EEPROM access. */
  outl(EE_ENB & ~EE_CS, ee_addr);
  if (debug > 2)
    printf(" EEPROM value at %d is %5.5x.\n", location, retval);
  return retval;
}


/* This executes a generic EEPROM command, typically a write or write enable.
   It returns the data output from the EEPROM, and thus may also be used for
   reads. */
static int do_eeprom_cmd(int ioaddr, int cmd, int cmd_len)
{
  unsigned retval = 0;
  int ee_addr = ioaddr;

  if (debug > 1)
    printf(" EEPROM op 0x%x: ", cmd);

  outl(EE_ENB | EE_SHIFT_CLK, ee_addr);

  /* Shift the command bits out. */
  do {
    short dataval = (cmd & (1 << cmd_len)) ? EE_WRITE_1 : EE_WRITE_0;
    outl(dataval, ee_addr);
    eeprom_delay();
    if (debug > 2)
      printf("%lX", inl(ee_addr) & 15);
    outl(dataval | EE_SHIFT_CLK, ee_addr);
    eeprom_delay();
    retval = (retval << 1) | ((inl(ee_addr) & EE_DATA_READ) ? 1 : 0);
  } while (--cmd_len >= 0);
  outl(EE_ENB, ee_addr);

  /* Terminate the EEPROM access. */
  outl(EE_ENB & ~EE_CS, ee_addr);
  if (debug > 1)
    printf(" EEPROM result is 0x%5.5x.\n", retval);
  return retval;
}


static int dcWriteRom(int ioaddr, int index, int new_value)
{
  int ee_addr_size = read_eeprom(ioaddr, 0xff, 8) & 0x40000 ? 8 : 6;
  int i;
  unsigned short newval;

  udelay(10*1000); /* test-only */

  if (debug > 1)
    {
      printf("ee_addr_size=%d.\n", ee_addr_size);
      printf("Writing new entry 0x%4.4x to offset %d.\n", new_value, index);
    }
  /* Enable programming modes. */
  do_eeprom_cmd(ioaddr, (0x4f << (ee_addr_size-4)), 3+ee_addr_size);
  /* Do the actual write. */
  do_eeprom_cmd(ioaddr,
                (((EE_WRITE_CMD<<ee_addr_size)|index) << 16) | new_value,
                3 + ee_addr_size + 16);
  /* Poll for write finished. */
  outl(EE_ENB, ioaddr);
  for (i = 0; i < 10000; i++)			/* Typical 2000 ticks */
    if (inl(ioaddr) & EE_DATA_READ)
      break;
  if (debug > 1)
    printf(" Write finished after %d ticks.\n", i);
  /* Disable programming. */
  do_eeprom_cmd(ioaddr, (0x40 << (ee_addr_size-4)), 3 + ee_addr_size);
  /* And read the result. */
  newval = do_eeprom_cmd(ioaddr,
                         (((EE_READ_CMD<<ee_addr_size)|index) << 16)
                         | 0xffff, 3 + ee_addr_size + 16);
  if (debug > 1)
    printf("  New value at offset %d is %4.4x.\n", index, newval);
  return 1;
}


static void updateSRom(bd_t *bis)
{
  unsigned short addrShort;

  /* taken from ramix pmc-module */
  dcWriteRom(iobase, 0x00, 0x140b);
  dcWriteRom(iobase, 0x01, 0x6610);
  dcWriteRom(iobase, 0x02, 0x0);
  dcWriteRom(iobase, 0x03, 0x0);
  dcWriteRom(iobase, 0x04, 0x0);
  dcWriteRom(iobase, 0x05, 0x0);
  dcWriteRom(iobase, 0x06, 0x0);
  dcWriteRom(iobase, 0x07, 0x0);

  dcWriteRom(iobase, 0x08, 0x00a3);
  dcWriteRom(iobase, 0x09, 0x0103);

  dcWriteRom(iobase, 0x0a, 0x0200);      /* Ethernet Addr... */
  dcWriteRom(iobase, 0x0b, 0x0027);
  addrShort = ((bis->bi_enetaddr[5] & 0xff) << 8) | (bis->bi_enetaddr[4] & 0xff);
  dcWriteRom(iobase, 0x0c, addrShort);      /* ...Ethernet Addr */

  dcWriteRom(iobase, 0x0d, 0x1f00);
  dcWriteRom(iobase, 0x0e, 0x0);
  dcWriteRom(iobase, 0x0f, 0x0);

  dcWriteRom(iobase, 0x10, 0x0108);
  dcWriteRom(iobase, 0x11, 0x038d);      /* set extended format bit 0x80 !!! */
  dcWriteRom(iobase, 0x12, 0x0);
  dcWriteRom(iobase, 0x13, 0x0);
  dcWriteRom(iobase, 0x14, 0xe078);
  dcWriteRom(iobase, 0x15, 0x0001);
  dcWriteRom(iobase, 0x16, 0x0040);
  dcWriteRom(iobase, 0x17, 0x0018);

  dcWriteRom(iobase, 0x18, 0x0);
  dcWriteRom(iobase, 0x19, 0x0);
  dcWriteRom(iobase, 0x1a, 0x0);
  dcWriteRom(iobase, 0x1b, 0x0);
  dcWriteRom(iobase, 0x1c, 0x0);
  dcWriteRom(iobase, 0x1d, 0x0);
  dcWriteRom(iobase, 0x1e, 0x0);
  dcWriteRom(iobase, 0x1f, 0x0);

  dcWriteRom(iobase, 0x20, 0x0);
  dcWriteRom(iobase, 0x21, 0x0);
  dcWriteRom(iobase, 0x22, 0x0);
  dcWriteRom(iobase, 0x23, 0x0);
  dcWriteRom(iobase, 0x24, 0x0);
  dcWriteRom(iobase, 0x25, 0x0);
  dcWriteRom(iobase, 0x26, 0x0);
  dcWriteRom(iobase, 0x27, 0x0);

  dcWriteRom(iobase, 0x28, 0x0);
  dcWriteRom(iobase, 0x29, 0x0);
  dcWriteRom(iobase, 0x2a, 0x0);
  dcWriteRom(iobase, 0x2b, 0x0);
  dcWriteRom(iobase, 0x2c, 0x0);
  dcWriteRom(iobase, 0x2d, 0x0);
  dcWriteRom(iobase, 0x2e, 0x0);
  dcWriteRom(iobase, 0x2f, 0x0);

  dcWriteRom(iobase, 0x30, 0x0);
  dcWriteRom(iobase, 0x31, 0x0);
  dcWriteRom(iobase, 0x32, 0x0);
  dcWriteRom(iobase, 0x33, 0x0);
  dcWriteRom(iobase, 0x34, 0x0);
  dcWriteRom(iobase, 0x35, 0x0);
  dcWriteRom(iobase, 0x36, 0x0);
  dcWriteRom(iobase, 0x37, 0x0);

  dcWriteRom(iobase, 0x38, 0x0);
  dcWriteRom(iobase, 0x39, 0x0);
  dcWriteRom(iobase, 0x3a, 0x0);
  dcWriteRom(iobase, 0x3b, 0x0);
  dcWriteRom(iobase, 0x3c, 0x0);
  dcWriteRom(iobase, 0x3d, 0x0);
  dcWriteRom(iobase, 0x3e, 0x0);
  dcWriteRom(iobase, 0x3f, 0x4e07);
}
