/*
 * Copyright 2011 Allied Telesis.
 */
#include <common.h>
#include <command.h>
#include <miiphy.h>
#include <98dx160.h>

#define DX160_SMI0			0x04004054

#define DX160_WRITE_TIMEOUT             10	/* ~10usec */
#define DX160_READ_TIMEOUT              10	/* ~10usec */

int dx160_reg_read(char *devname, int smi_addr, u32 reg_addr, u32 * reg_data)
{
	int to;
	u16 status;
	u16 msb;
	u16 lsb;

	/* Setup read address */
	msb = reg_addr >> 16;
	lsb = reg_addr & 0xffff;

	miiphy_write(devname, smi_addr, DX160_READ_ADDR_MSB, msb);
	miiphy_write(devname, smi_addr, DX160_READ_ADDR_LSB, lsb);

	/* Poll for read ready */
	to = DX160_READ_TIMEOUT;
	while (--to != 0) {
		miiphy_read(devname, smi_addr, DX160_READ_WRITE_STATUS,
			    &status);
		if ((status & DX160_SMI_READ_RDY) != 0)
			break;
		udelay(1);
	}

	if (to == 0) {
		printf("DX160: read timed-out\n");
		return 1;
	}

	/* Read the data */
	miiphy_read(devname, smi_addr, DX160_READ_DATA_MSB, &msb);
	miiphy_read(devname, smi_addr, DX160_READ_DATA_LSB, &lsb);

	*reg_data = (msb << 16) | lsb;

	return 0;
}

int dx160_reg_write(char *devname, int smi_addr, u32 reg_addr, u32 reg_data)
{
	int to;
	u16 status;
	u16 msb;
	u16 lsb;

	/* Poll for previous write complete */
	to = DX160_WRITE_TIMEOUT;
	while (--to != 0) {
		miiphy_read(devname, smi_addr, DX160_READ_WRITE_STATUS,
			    &status);
		if ((status & DX160_SMI_WRITE_DONE) != 0)
			break;
		udelay(1);
	}

	if (to == 0) {
		printf("DX160: write timed-out\n");
		return 1;
	}

	/* Setup write address */
	msb = reg_addr >> 16;
	lsb = reg_addr & 0xffff;

	miiphy_write(devname, smi_addr, DX160_WRITE_ADDR_MSB, msb);
	miiphy_write(devname, smi_addr, DX160_WRITE_ADDR_LSB, lsb);

	/* Write the data */
	msb = reg_data >> 16;
	lsb = reg_data & 0xffff;

	miiphy_write(devname, smi_addr, DX160_WRITE_DATA_MSB, msb);
	miiphy_write(devname, smi_addr, DX160_WRITE_DATA_LSB, lsb);

	return 0;
}

int dx160_mii_write(char *devname, int smi_addr, u8 phy_addr, u8 reg_addr,
		    u16 reg_data)
{
	u32 value =
	    (reg_data & 0xffff) | ((phy_addr & 0x1F) << 16) | ((reg_addr & 0x1F)
							       << 21);

	dx160_reg_write(devname, smi_addr, DX160_SMI0, value);
	udelay(1000);
	return 1;
}

int dx160_mii_read(char *devname, int smi_addr, u8 phy_addr, u8 reg_addr,
		   u16 *reg_data)
{
	u32 value =
	    ((phy_addr & 0x1F) << 16) | ((reg_addr & 0x1F) << 21) | (1 << 26);

	dx160_reg_write(devname, smi_addr, DX160_SMI0, value);
	udelay(1000);
	dx160_reg_read(devname, smi_addr, DX160_SMI0, &value);
	udelay(1000);
	*reg_data = (u16) (value & 0xFFFF);
	return 1;
}

int dx160_config(char *devname, u8 smi_addr,
		 struct dx160_config_reg const *config)
{
	int rc;
	u32 data;

	BUG_ON(devname == NULL);
	BUG_ON(smi_addr == 0);

	/* Sanity check. Are we are in communication with a DX160? */
	if ((rc = dx160_reg_read(devname, smi_addr, 0x4c, &data))) {
		printf("DX160: read failed addr=%08x (rc=%d)\n", 0x4c, rc);
		return 1;
	}

	printf("DX160: DeviceID = %05x\n", data);

	/* Go write all the required configuration */
	while (config->mask != 0) {
		/* Sanity check the mask and data */
		if ((config->data & ~config->mask) != 0) {
			printf
			    ("DX160: error addr=%08x mask=%08x data=%08x\n",
			     config->addr, config->mask, config->data);
			return 1;
		}

		/* Read */
		rc = dx160_reg_read(devname, smi_addr, config->addr, &data);
		if (rc) {
			printf("DX160: read failed addr=%08x (rc=%d)\n",
			       config->addr, rc);
			return 1;
		}

		/* Modify */
		data &= ~config->mask;
		data |= (config->data & config->mask);

		/* Write */
		rc = dx160_reg_write(devname, smi_addr, config->addr, data);
		if (rc) {
			printf("DX160: write failed addr=%08x (rc=%d)\n",
			       config->addr, rc);
			return 1;
		}

		/* Move on */
		config++;
	}

	return 0;
}

typedef struct {
	char *name;
	unsigned int address;
} dx160RegTableEntry;

const dx160RegTableEntry dx160Regs[] = {
/* regName             regAddr */
	{"globalControl", 0x00000000},
	{"globalConfig", 0x00000004},
	{"deviceID", 0x0000004C},
	{"cpuPortCtrlReg", 0x000000A0},
	{"cpuPortStatusReg", 0x000000A4},
	{"addrUpdate0", 0x06000040},
	{"addrUpdate1", 0x06000044},
	{"addrUpdate2", 0x06000048},
	{"addrUpdate3", 0x0600004c},
	{"auQBaseAddr", 0x06000034},
	{"auQControl", 0x06000038},

	{"cpuPortControl", 0x02018000},
	{"cntrSetCfg0", 0x020400DC},
	{"cntrSetInPckt0", 0x020400E0},
	{"cntrSetInFltPckt0", 0x020400E4},
	{"cntrSetSecFltPckt0", 0x020400E8},
	{"cntrSetBrgFltPckt0", 0x020400EC},
	{"cntrSetCfg1", 0x020400F0},
	{"cntrSetInPckt1", 0x020400F4},
	{"cntrSetInFltPckt1", 0x020400F8},
	{"cntrSetSecFltPckt1", 0x020400FC},
	{"cntrSetBrgFltPckt1", 0x02040100},
	{"userPrio2TrfClass", 0x02040004},
	{"trfClass2UserPrio", 0x0204002C},
	{"forceL3cos", 0x0204000C},
	{"mapCpuCode2Prio", 0x02040028},
	{"dscp2cos", 0x02040200},
	{"hostInPckt", 0x020400BC},
	{"hostOutPckt", 0x020400C0},
	{"hostOutMcPckt", 0x020400CC},
	{"hostOutBrdPckt", 0x020400D0},
	{"ingFltStatus", 0x02040038},
	{"egressFilter", 0x0180000C},
	{"l2IngressControl", 0x02040000},
	{"learnedDisc", 0x06000030},
	{"macTblAction0", 0x06000004},
	{"macTblAction1", 0x06000008},
	{"macTblAction2", 0x06000020},
	{"trunkTable0", 0x02040170},
	{"trunkTable1", 0x02040174},
	{"trunkTable2", 0x02040178},
	{"trunkTable3", 0x0204017c},
	{"vlan0", 0x020400A8},
	{"vlan1", 0x020400AC},
	{"unknRateLimit0", 0x02040168},
	{"unknRateLimit1", 0x0204016C},
	{"macControlReg", 0x06000000},
	{"macRangeFltrCmd", 0x02040074},
	{"macRangeFltMirrorCmd", 0x02040034},
	{"macRangeFltrBase0_0", 0x02040078},
	{"macRangeFltrBase0_1", 0x0204007c},
	{"macRangeFltrBase0_2", 0x02040080},
	{"macRangeFltrBase1_0", 0x02040084},
	{"macRangeFltrBase1_1", 0x02040088},
	{"macRangeFltrBase1_2", 0x0204008c},
	{"macRangeFltrBase2_0", 0x02040090},
	{"macRangeFltrBase2_1", 0x02040094},
	{"macRangeFltrBase2_2", 0x02040098},
	{"macRangeFltrBase3_0", 0x0204009c},
	{"macRangeFltrBase3_1", 0x020400a0},
	{"macRangeFltrBase3_2", 0x020400a4},

	{"macCtrl", 0x04000400},
	{"macCtrl", 0x04000500},
	{"macCtrl", 0x05000000},
	{"macCtrl", 0x05000100},
	{"macStatus", 0x04000404},
	{"macStatus", 0x04000504},
	{"macStatus", 0x05000004},
	{"macStatus", 0x05000104},
	{"autoNegCtrl", 0x04000408},
	{"autoNegCtrl", 0x04000508},
	{"autoNegCtrl", 0x05000008},
	{"autoNegCtrl", 0x05000108},
	{"serdesCnfg", 0x04000410},
	{"serdesCnfg", 0x04000510},
	{"serdesCnfg", 0x05000010},
	{"serdesCnfg", 0x05000110},
	{"portControl", 0x02004000},
	{"portControl", 0x02005000},
	{"portControl", 0x0200c000},
	{"portControl", 0x0200d000},
	{"portVid", 0x02004004},
	{"portVid", 0x0200c004},

	{"goodOctetsRcv", 0x04010200},
	{"goodOctetsRcv", 0x04010280},
	{"goodOctetsRcv", 0x05010000},
	{"goodOctetsRcv", 0x05010080},
	{"goodOctetsRcv1", 0x04010204},
	{"goodOctetsRcv1", 0x04010284},
	{"goodOctetsRcv1", 0x05010004},
	{"goodOctetsRcv1", 0x05010084},
	{"badOctetsRcv", 0x04010208},
	{"badOctetsRcv", 0x04010288},
	{"badOctetsRcv", 0x05010008},
	{"badOctetsRcv", 0x05010088},
	{"macTransmitErr", 0x0401020c},
	{"macTransmitErr", 0x0401028c},
	{"macTransmitErr", 0x0501000c},
	{"macTransmitErr", 0x0501008c},
	{"goodPktsRcv", 0x04010210},
	{"goodPktsRcv", 0x04010290},
	{"goodPktsRcv", 0x05010010},
	{"goodPktsRcv", 0x05010090},
	{"badPktsRcv", 0x04010214},
	{"badPktsRcv", 0x04010294},
	{"badPktsRcv", 0x05010014},
	{"badPktsRcv", 0x05010094},
	{"broadcastPktsRcv", 0x04010218},
	{"broadcastPktsRcv", 0x04010298},
	{"broadcastPktsRcv", 0x05010018},
	{"broadcastPktsRcv", 0x05010098},
	{"multicastPktsRcv", 0x0401021c},
	{"multicastPktsRcv", 0x0401029c},
	{"multicastPktsRcv", 0x0501001c},
	{"multicastPktsRcv", 0x0501009c},
	{"goodOctetsSent", 0x04010238},
	{"goodOctetsSent", 0x040102b8},
	{"goodOctetsSent", 0x05010038},
	{"goodOctetsSent", 0x050100b8},
	{"goodOctetsSent1", 0x0401023c},
	{"goodOctetsSent1", 0x040102bc},
	{"goodOctetsSent1", 0x0501003c},
	{"goodOctetsSent1", 0x050100bc},
	{"goodPktsSent", 0x04010240},
	{"goodPktsSent", 0x040102c0},
	{"goodPktsSent", 0x05010040},
	{"goodPktsSent", 0x050100c0},
	{"excessiveCollisions", 0x04010244},
	{"excessiveCollisions", 0x040102c4},
	{"excessiveCollisions", 0x05010044},
	{"excessiveCollisions", 0x050100c4},
	{"multicastPktsSent", 0x04010248},
	{"multicastPktsSent", 0x040102c8},
	{"multicastPktsSent", 0x05010048},
	{"multicastPktsSent", 0x050100c8},
	{"broadcastPktsSent", 0x0401024c},
	{"broadcastPktsSent", 0x040102cc},
	{"broadcastPktsSent", 0x0501004c},
	{"broadcastPktsSent", 0x050100cc},
	{"unrecogMacCntrRcv", 0x04010250},
	{"unrecogMacCntrRcv", 0x040102d0},
	{"unrecogMacCntrRcv", 0x05010050},
	{"unrecogMacCntrRcv", 0x050100d0},
	{"flowCtrlSent", 0x04010254},
	{"flowCtrlSent", 0x040102d4},
	{"flowCtrlSent", 0x05010054},
	{"flowCtrlSent", 0x050100d4},
	{"goodFlowCtrlRcv", 0x04010258},
	{"goodFlowCtrlRcv", 0x040102d8},
	{"goodFlowCtrlRcv", 0x05010058},
	{"goodFlowCtrlRcv", 0x050100d8},
	{"dropEvents", 0x0401025c},
	{"dropEvents", 0x040102dc},
	{"dropEvents", 0x0501005c},
	{"dropEvents", 0x050100dc},
	{"undersizePkts", 0x04010260},
	{"undersizePkts", 0x040102e0},
	{"undersizePkts", 0x05010060},
	{"undersizePkts", 0x050100e0},
	{"fragmentPkts", 0x04010264},
	{"fragmentPkts", 0x040102e4},
	{"fragmentPkts", 0x05010064},
	{"fragmentPkts", 0x050100e4},
	{"oversizePkts", 0x04010268},
	{"oversizePkts", 0x040102e8},
	{"oversizePkts", 0x05010068},
	{"oversizePkts", 0x050100e8},
	{"jabberPkts", 0x0401026c},
	{"jabberPkts", 0x040102ec},
	{"jabberPkts", 0x0501006c},
	{"jabberPkts", 0x050100ec},
	{"macRcvError", 0x04010270},
	{"macRcvError", 0x040102f0},
	{"macRcvError", 0x05010070},
	{"macRcvError", 0x050100f0},
	{"badCRC", 0x04010274},
	{"badCRC", 0x040102f4},
	{"badCRC", 0x05010074},
	{"badCRC", 0x050100f4},
	{"collisions", 0x04010278},
	{"collisions", 0x040102f8},
	{"collisions", 0x05010078},
	{"collisions", 0x050100f8},
	{"lateCollisions", 0x0401027c},
	{"lateCollisions", 0x040102fc},
	{"lateCollisions", 0x0501007c},
	{"lateCollisions", 0x050100fc},

	{"txQueueConfig", 0x01800004},
	{"txQMcFifoEccConfig", 0x01850000},
	{"txQDescrLimit0TC0", 0x01800C00},
	{"txQDescrLimit0TC1", 0x01800C10},
	{"txQDescrLimit0TC2", 0x01800C20},
	{"txQDescrLimit0TC3", 0x01800C30},
	{"txQPortDescrLimit0", 0x01800C80},
	{"cntTxQConfig0", 0x01800E00},
	{"cntOutUcPkts0", 0x01800E04},
	{"cntOutMcPkts0", 0x01800E08},
	{"cntOutBytePkts0", 0x01800E0C},
	{"cntBrgEgrFiltered0", 0x01800F00},
	{"cntTxQFiltered0", 0x01800F04},
	{"txQDescrLimit1TC0", 0x01810C00},
	{"txQDescrLimit1TC1", 0x01810C10},
	{"txQDescrLimit1TC2", 0x01810C20},
	{"txQDescrLimit1TC3", 0x01810C30},
	{"txQPortDescrLimit1", 0x01810C80},
	{"cntTxQConfig1", 0x01801E00},
	{"cntOutUcPkts1", 0x01801E04},
	{"cntOutMcPkts1", 0x01801E08},
	{"cntOutBytePkts1", 0x01801E0C},
	{"cntBrgEgrFiltered1", 0x01801F00},
	{"cntTxQFiltered1", 0x01801F04},

	{"intSumCause", 0x00000030},
	{"intSumMask", 0x00000034},
	{"ethBrgCause", 0x02040130},
	{"ethBrgMask", 0x02040134},
	{"bmIntCause", 0x03000040},
	{"bmIntMask", 0x03000044},
	{"miscIntCause", 0x00000038},
	{"miscIntMask", 0x0000003C},
	{"txqIntCause", 0x01800700},
	{"txqIntMask", 0x01800704},
	{"mtIntCause", 0x06000018},
	{"mtIntMask", 0x0600001c},
	{"gopCauseReg0_0", 0x04004000},
	{"gopMaskReg0_0", 0x04004004},
	{"gopCauseReg1_0", 0x04004008},
	{"gopMaskReg1_0", 0x0400400C},
	{"gopCauseReg0_1", 0x04804000},
	{"gopMaskReg0_1", 0x04804004},
	{"gopCauseReg1_1", 0x04804008},
	{"gopMaskReg1_1", 0x0480400C},
	{"gopCauseReg0_2", 0x05004000},
	{"gopMaskReg0_2", 0x05004004},
	{"gopCauseReg1_2", 0x05004008},
	{"gopMaskReg1_2", 0x0500400C},
	{"gopCauseReg0_3", 0x05804000},
	{"gopMaskReg0_3", 0x05804004},
	{"gopCauseReg1_3", 0x05804008},
	{"gopMaskReg1_3", 0x0580400C},

	{"portFlowCntrlThr", 0x03000024},
	{"portRxBufLimit", 0x03000024},
	{"bufLimitProfileAssoc", 0x03000030},
	{"bufMngGlobalLimit", 0x03000000},
	{"cpuRxBufLimit", 0x03000020},
	{"cpuRxBufCount", 0x0300017C},
	{"buffMngParam", 0x07800004},

	{"smi0Control", 0x04004054},
	{"smi1Control", 0x05004054},
	{"smiPhyAddr0", 0x04004030},
	{"smiPhyAddr1", 0x04804030},
	{"smiPhyAddr2", 0x05004030},
	{"smiPhyAddr3", 0x05804030},
	{"smiGOP0", 0x04004034},
	{"smiGOP1", 0x04804034},
	{"smiGOP2", 0x05004034},
	{"smiGOP3", 0x05804034},

	{"ledInt0Control0", 0x04004100},
	{"ledInt1Control0", 0x05004100},
	{"ledInt0Class01Manip", 0x04004108},
	{"ledInt1Class01Manip", 0x05004108},
	{"ledInt0Class23Manip", 0x04804108},
	{"ledInt1Class23Manip", 0x05804108},
	{"ledInt0Class4Manip", 0x0400410c},
	{"ledInt1Class4Manip", 0x0500410c},
	{"ledInt0Class5Manip", 0x0480410c},
	{"ledInt1Class5Manip", 0x0580410c},
	{"ledInt0Class6Manip", 0x04804100},
	{"ledInt1Class6Manip", 0x05804100},
	{"ledInt0Group01Config", 0x04004104},
	{"ledInt1Group01Config", 0x05004104},
	{"ledInt0Group23Config", 0x04804104},
	{"ledInt1Group23Config", 0x05804104},
	{NULL, 0}
};

//Read all the registers. For some reason this gets the link up on SBx8100 CFC.
void dx160_read_all(void)
{
	unsigned int reg;
	unsigned int data;
	char *devname;
	const dx160RegTableEntry *tablept = &dx160Regs[0];

	devname = miiphy_get_current_dev();

	while (tablept->name != NULL) {
		reg = tablept->address;
		dx160_reg_read (devname, CONFIG_98DX160_ADDR, reg, &data);
		tablept++;
	}
}

static void extract_range(char *input, unsigned int *plo, unsigned int *phi)
{
	char *end;
	*plo = simple_strtoul(input, &end, 16);
	if (*end == '-') {
		end++;
		*phi = simple_strtoul(end, NULL, 16);
	} else {
		*phi = *plo;
	}
}

int do_dx160(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	char op[2] = {};
	unsigned int reglo = 0, reghi = 0;
	unsigned int reg;
	unsigned int data;
	unsigned short data16;
	char *devname;
	const dx160RegTableEntry *tablept = &dx160Regs[0];
	int rcode = 0;

	if (argc < 2) {
		cmd_usage(cmdtp);
		return 1;
	}

	if ((flag & CMD_FLAG_REPEAT) == 0) {
		op[0] = argv[1][0];
		if (strlen(argv[1]) > 1)
			op[1] = argv[1][1];
		else
			op[1] = '\0';

		if (argc >= 3)
			extract_range(argv[2], &reglo, &reghi);
		if (argc >= 4)
			data = simple_strtoul(argv[3], NULL, 16);
	}

	/* use current device */
	devname = miiphy_get_current_dev();

	if (op[0] == 'r') {
		for (reg = reglo; reg <= reghi; reg++) {
			data = 0xffffffff;
			if (dx160_reg_read
			    (devname, CONFIG_98DX160_ADDR, reg, &data) != 0) {
				printf
				    ("Error reading from the DX160 reg=%02x\n",
				     reg);
				rcode = 1;
			} else {
				printf("%08x=", (uint) reg);
				printf("%08X\n", data & 0xFFFFFFFF);
			}
		}
	} else if (op[0] == 'w') {
		for (reg = reglo; reg <= reghi; reg++) {
			if (dx160_reg_write
			    (devname, CONFIG_98DX160_ADDR, reg, data) != 0) {
				printf("Error writing to the DX160 reg=%08x\n",
				       reg);
				rcode = 1;
			}
		}
	} else if (op[0] == 'd') {
		while (tablept->name != NULL) {
			if (argc == 2
			    || strncmp(argv[2], tablept->name,
				       strlen(argv[2])) == 0) {
				reg = tablept->address;
				if (dx160_reg_read
				    (devname, CONFIG_98DX160_ADDR, reg,
				     &data) != 0) {
					printf
					    ("Error reading from the DX160 reg=%08x(%s)\n",
					     reg, tablept->name);
					rcode = 1;
				} else {
					printf("%08x(%s)=", (uint) reg,
					       tablept->name);
					printf("%08X\n", data & 0xFFFFFFFF);
				}
			}
			tablept++;
		}
	} else if (op[0] == 'p') {
		reg = simple_strtoul(argv[3], NULL, 16);
		data16 = 0xFFFF;
		if (dx160_mii_read
		    (devname, CONFIG_98DX160_ADDR,
		     simple_strtoul(argv[2], NULL, 16), reg, &data16) != 1) {
			printf("Error reading from the DX160 reg=%02x\n", reg);
			rcode = 1;
		} else {
			printf("%02x=", (uint) reg);
			printf("%04X\n", data16 & 0xFFFF);
		}
	}
	return rcode;
}

U_BOOT_CMD(dx160, 4, 1, do_dx160,
	   "dx160 utility commands",
	   "dx160 read  <addr>        - read  dx160 <addr> register\n"
	   "dx160 write <addr> <data> - write dx160 <addr> register\n"
	   "dx160 phy <smi> <addr>    - read  phy at <smi> <addr> register\n"
	   "dx160 dump  <addr>        - dump named register(s)");
