#include "archinc.h"
#include "cli.h"
#include "strutil.h"
#include "zarlink.h"
#include "zl_bus.h"
#include "zl_mii.h"
#include "zlregs.h"

#ifdef CONFIG_INCLUDE_ZARLINK

#include <stdio.h>
#include <stdlib.h>

typedef struct {
  int offset;
  char * name;
  } miiReg_t;

static const miiReg_t miiRegs[12] = {{0x00, "Basic Control   "},
                                     {0x01, "Basic Status    "},
                                     {0x02, "PHY Id 1        "},
                                     {0x03, "PHY Id 2        "},
                                     {0x04, "Autoneg Advert  "},
                                     {0x05, "Autoneg Partner "},
                                     {0x06, "Autoneg Expans  "},
                                     {0x07, "Autoneg Next Pg "},
                                     {0x08, "Lnk Prt Next Pg "},
                                     {0x15, "RXER Counter    "},
                                     {0x1b, "IRQ Ctrl/Status "},
                                     {0x1f, "100BaseTX PHY   "}};

static unsigned short ctrlShadow[6];

/*
 * Zarlink MII interface write routine
 *
 * 1. set MII Management State Machine Disable (bit[6]) in FEN register;
 * 2. wait until RDY is set (bit [7]) in MIIC3;
 * 3. set low bit 8 of new MII register value in MIIC0;
 * 4. set high bit 8 of new MII register value in MIIC1;
 * 5. set (0x20 | (reg_num & 0x1F)) in MIIC2;
 * 6. set (phy_addr & 0x1F) in MIIC3;
 * 7. unset MII Management State Machine Disable (bit [6]) in FEN;
 *
 */

int zarlinkMiiWrite(unsigned char miiPhy, unsigned char miiReg, unsigned short miiCmd)
{
  unsigned char tmpReg;
  unsigned char fenShadow;
  int i;

  fenShadow = zarlinkIndexedRead(FEN);
  zarlinkIndexedWrite(FEN, (fenShadow | MII_STATE_MACHINE_DISABLE));

  for(i=0;i<1000;i++) {
    tmpReg = zarlinkIndexedRead(MIIC3);
    if ((tmpReg & MII_READY) != 0)
      break;
    }

  if ((tmpReg & MII_READY) == 0)
    return -1;

  zarlinkIndexedWrite(MIIC0, (unsigned char)(miiCmd & 0x00FF));
  zarlinkIndexedWrite(MIIC1, (unsigned char)((miiCmd >> 8) & 0x00FF));
  zarlinkIndexedWrite(MIIC2, (unsigned char)(MII_WRITE_OP | (miiReg & 0x1F)));
  zarlinkIndexedWrite(MIIC3, (miiPhy & 0x1F));

  zarlinkIndexedWrite(FEN, fenShadow);

  return 0;
}

/* 
 * Zarlink MII interface read routine
 *
 * 1. set MII Management State Machine Disable (bit [6]) in FEN register;
 * 2. wait until RDY is set (bit [7]) in MIIC3;
 * 3. set (0x40 | (reg_num & 0x1F)) in MIIC2;
 * 4. set (phy_addr & 0x1F) in MIIC3;
 * 5. wait until RDY (bit [7]) AND VALID (bit [6]) are set in MIIC3;
 * 6. read low bit 8 of new_value in MIID0;
 * 7. read high bit 8 of new_value in MIID1;
 * 8. unset MII Management State Machine Disable (bit [6]) in FEN;
 *
 */

int zarlinkMiiRead(unsigned char miiPhy, unsigned char miiReg, unsigned short * pMiiData)
{
  unsigned short result;
  unsigned char tmpReg;
  unsigned char fenShadow;
  int i;

  fenShadow = zarlinkIndexedRead(FEN);
  zarlinkIndexedWrite(FEN, (fenShadow | MII_STATE_MACHINE_DISABLE));

  for(i=0;i<1000;i++) {
    tmpReg = zarlinkIndexedRead(MIIC3);
    if ((tmpReg & MII_READY) != 0)
      break;
    }

  if ((tmpReg & MII_READY) == 0)
    return -1;
  
  zarlinkIndexedWrite(MIIC2, (MII_READ_OP | (miiReg & 0x1F)));
  zarlinkIndexedWrite(MIIC3, (miiPhy & 0x1F));

  for(i=0;i<1000;i++) {
    tmpReg = zarlinkIndexedRead(MIIC3);
    if ((tmpReg & MII_READY_VALID) == MII_READY_VALID)
      break;
    }

  if ((tmpReg & MII_READY_VALID) != MII_READY_VALID)
      return -2;

  result = (unsigned short)zarlinkIndexedRead(MIID0);
  result |= (unsigned short)((zarlinkIndexedRead(MIID1) << 8) & 0xff00);

  zarlinkIndexedWrite(FEN, fenShadow);

  *pMiiData = result;
  return 0;
}

int zarlinkMiiDump(int phy)
{
  char * pOutBuf;
  int bufLen;
  int i;
  unsigned short phyData;
  int retVal;

  if ((phy < 8) || (phy > 13))
    return retVal;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  sprintf(pOutBuf, "\r\ndumping all regs for phy [%d]\r\n", phy);
  clicharSendOutputBuffer(pOutBuf, bufLen);
  
  retVal = 0;
  for(i=0;((i<12) && (retVal == 0));i++) {
    retVal = zarlinkMiiRead((unsigned char)phy, (unsigned char)(miiRegs[i].offset), &phyData);
    if (retVal == 0) {
      sprintf(pOutBuf, "%s [%#04x]\r\n", miiRegs[i].name, phyData);
      clicharSendOutputBuffer(pOutBuf, bufLen);
      }
    }

  return retVal;
}

/*
 * look at a phy and if it's not up, put it into 'power save' mode
 * if return value is zero, then no action was taken. If it's 1, 
 * then the PHY has been powered down
 */

int zarlinkMiiPowerSave(int phy)
{
  unsigned short phyData;
  int retVal = -1;

  if ((phy < 8) || (phy > 13))
    return retVal;

  retVal = zarlinkMiiRead((unsigned char)phy, (unsigned char)0x01, &phyData);

  if (retVal == 0) {
    if ((phyData & 0x0004) == 0) {
      // link is down
      retVal = zarlinkMiiWrite((unsigned char)phy, (unsigned char)0x00, (unsigned short)0x0800);
      }
    else {
      retVal = 1;
      // link is up
      }
    }

  return retVal;

}

int zarlinkMiiCtrlRestore(int phy)
{
  if ((phy < 8) || (phy > 13))
    return -1;

  return zarlinkMiiWrite((unsigned char)phy, (unsigned char)0x00, ctrlShadow[MII_PHY_TO_PORT(phy)]);
}

int zarlinkMiiPowerSaveForce(int phy)
{
  unsigned short phyData;
  int retVal = -1;

  if ((phy < 8) || (phy > 13))
    return retVal;

  zarlinkMiiRead((unsigned char)phy, (unsigned char)0x00, &(ctrlShadow[MII_PHY_TO_PORT(phy)]) );

  retVal = zarlinkMiiWrite((unsigned char)phy, (unsigned char)0x00, (unsigned short)0x0800);

  return retVal;

}
static int phypwrHandler(int argc, char *argv[], int flags)
{
  int retVal = -1;
  int phyNum;
  char * pOutBuf;
  int bufLen;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  if (argc != 2) {
    return retVal;
    }

  phyNum = atoi(argv[1]);
  retVal = zarlinkMiiPowerSave(phyNum);

  if (retVal == 1) {
    sprintf(pOutBuf, "%s: link is up, cannot power down PHY[%d]!\r\n", argv[0], phyNum);
    }
  else if (retVal == 0) {
    sprintf(pOutBuf, "%s: PHY[%d] has been powered down\r\n", argv[0], phyNum);
    }
  else {
    sprintf(pOutBuf, "%s: invalid PHY[%d] number\r\n", argv[0], phyNum);
    }
  clicharSendOutputBuffer(pOutBuf, bufLen);

  return retVal;
}

static int defaultPhy = -1;

static int miiHandler(int argc, char * argv[], int flags)
{
  int retVal = -1;
  int i;
  unsigned short phyData;
  unsigned char phyAddr;
  unsigned char phyReg; 
  unsigned char phyNum;
  char * pOutBuf;
  int bufLen;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  switch(*argv[1]) {
    case 'p':
    case 'P':
      if (argc == 3) {
        defaultPhy = atoi(argv[2]);
        sprintf(pOutBuf, "mii default phy set to [%02x]\r\n", defaultPhy);
        retVal = 0;
        }
       // set default phy
       break;
    case 'r':
    case 'R':
      if (defaultPhy == -1) {
        sprintf(pOutBuf, "! mii default phy not set\r\n");
        break;
        }
      phyReg = (unsigned char)strtol(argv[2], NULL, 16);
      retVal = zarlinkMiiRead(((unsigned char)defaultPhy), phyReg, &phyData);
      if (retVal == 0) {
        sprintf(pOutBuf, "mii read phy[%02x] reg[%02x] = %04x  OK\r\n", defaultPhy, phyReg, phyData);
        }
      else {
        sprintf(pOutBuf, "mii read phy[%02x] reg[%02x]  ERROR %d\r\n", defaultPhy, phyReg, retVal);
        }
      break;
    case 'd':
    case 'D':
      if (defaultPhy == -1) {
        sprintf(pOutBuf, "! mii default phy not set\r\n");
        break;
        }
      retVal = zarlinkMiiDump(((unsigned char)defaultPhy));
      break;
    case 'a':
    case 'A':
      retVal = 0;
      for(i=8;((i<14) && (retVal == 0)) ;i++) {
        retVal = zarlinkMiiDump(((unsigned char)i));
        }
      pOutBuf[0] = '\0';
      break;
    case 'w':
    case 'W':
      if (defaultPhy == -1) {
        sprintf(pOutBuf, "! mii default phy not set\r\n");
        break;
        }
      phyReg = (unsigned char)strtol(argv[2], NULL, 16);
      phyData = (unsigned short)strtol(argv[3], NULL, 16);
      retVal = zarlinkMiiWrite(((unsigned char)defaultPhy), phyReg, phyData);
      if (retVal == 0) {
        sprintf(pOutBuf, "mii write phy[%02x] reg[%02x] <-- %04x  OK\r\n", defaultPhy, phyReg, phyData);
        }
      else {
        sprintf(pOutBuf, "mii write phy[%02x] reg[%02x]  ERROR %d\r\n", defaultPhy, phyReg, retVal);
        }
      break;
    case 'x':
    case 'X':
      if (argc == 3) 
        phyNum = (unsigned char)strtol(argv[2], NULL, 16);
      else
        phyNum = (unsigned char)defaultPhy;
      retVal = zarlinkMiiWrite(phyNum, 0, 0x8000);
      sprintf(pOutBuf, "mii phy[%02x] has been reset\r\n", phyNum);
    default:
      break;
    }

  clicharSendOutputBuffer(pOutBuf, bufLen);
  return retVal;
}

int zarlinkMiiInit(void)
{
  int i;
  int retVal;
  unsigned short temp;

  for(i=0;i<6;i++) {
    retVal = zarlinkMiiRead((unsigned char)(MII_PORT_TO_PHY(i)), (unsigned char)0, &temp);
    if (retVal == 0) {
      ctrlShadow[i] = temp;
      }
    else {
      break;
      }
    }

  cliRegisterCommand("mii", miiHandler, "mii [(p)hy <defaultPhy>] [(r)ead <address>] [(w)rite <address> <data>] [(a)llregs] [(d)umpregs] [x](reset)");
  cliRegisterCommand("phypwr", phypwrHandler, "phypwr [phy] - power down phy if no link detected");
  return retVal;
}

#endif

