/*
 * (C) Copyright 2011
 * Akira Tosa, Allied Telesis R&D center K.K.
 */

/*
 * functions for manufacturing test
 */
#include <common.h>
#include <command.h>
#include <i2c.h>
#include "miim.h"

#define	X210MII_READ22	0
#define	X210MII_WRITE22	1
#define	CMIC_GPIO_OUT		0x0064 >> 2
#define	CMIC_GPIO_OE		0x0068 >> 2
#define	CMIC_SOFT_RESET_REG	0x0580 >> 2

#define	I2C_ADDR_RTC	0x68

int	x210_mii_acc (int rw, ulong phy_addr, ulong reg_addr, ulong write_value)
{
	ushort	read_value;

	if (rw==X210MII_READ22)	{
		serial_printf("PHY read,  PHY: 0x%02x, REG: 0x%02x, ", (uchar)phy_addr, (uchar)reg_addr);
		if (bcm5331x_miiphy_read (NULL, (uchar)phy_addr, (uchar)reg_addr, &read_value))	{
			serial_printf(" MII read timeout.\n");
			return	-1;
		}
		else	{
			serial_printf("VALUE: 0x%04x\n", read_value);
			return	(int)read_value;
		}
	}
	else if (rw==X210MII_WRITE22)	{
		serial_printf("PHY write, PHY: 0x%02x, REG: 0x%02x, VALUE: 0x%04x",
			(uchar)phy_addr, (uchar)reg_addr, (ushort)write_value);
		if (bcm5331x_miiphy_write (NULL, (uchar)phy_addr, (uchar)reg_addr, (ushort)write_value))	{
			serial_printf(" MII write timeout.\n");
			return	-1;
		}
		else	{
			serial_printf("\n");
			return	0;
		}
	}
	return	-1;
}

int do_mfr_test (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	volatile ulong * cmic_base_p	= (volatile ulong *)0xA8000000;
	volatile ulong * gpio_base_p	= (volatile ulong *)0xB8000000;
	volatile unsigned short * pld_p	= (volatile unsigned short *)(CONFIG_SYS_PLD_BASE);
	uchar	data, sec_alarm, duration;

	int	port, gpio_no;
	ushort	phy_addr;
	ulong	value;

	if (argc!=3)	{
		cmd_usage(cmdtp);
		return	1;
	}

	if (strcmp(argv[1], "as") == 0)	{	/* Auto standby */
		duration	= (uchar)simple_strtoul(argv[2], NULL, 10);
		if (duration)	{
			serial_printf("\nduration for standby mode: %d\n\n", duration);
			pld_p[1]	= 0x0;	/* select RTC for I2C clock */
			data	= 0x00;
			i2c_write(I2C_ADDR_RTC, 0xe, 1, &data, 1);	/* disable interrupt */
			i2c_write(I2C_ADDR_RTC, 0xf, 1, &data, 1);	/* clear interrupt */
			data	= 0x80;
			i2c_write(I2C_ADDR_RTC, 0x8, 1, &data, 1);	/* Alarm1 minutes */
			i2c_write(I2C_ADDR_RTC, 0x9, 1, &data, 1);	/* Alarm1 hours */
			i2c_write(I2C_ADDR_RTC, 0xa, 1, &data, 1);	/* Alarm1 day/date */

			i2c_read(I2C_ADDR_RTC, 0x0, 1, &data, 1);	/* read current second */
			sec_alarm	= (((data>>4)*10 + (data&0xf)) + duration) % 60;
			sec_alarm	= ((sec_alarm / 10)<<4) | (sec_alarm % 10);
			i2c_write(I2C_ADDR_RTC, 0x7, 1, &sec_alarm, 1);	/* Alarm1 seconds */

			data	= 0x05;
			i2c_write(I2C_ADDR_RTC, 0xe, 1, &data, 1);	/* enable interrupt */

			pld_p[6]	= 0xFB;	/* release RTC int mask */
			pld_p[7]	= 0x04;	/* clear RTC interrupt */
			pld_p[5]	= 0x5a;	/* enter Auto Standby mode */
		}
		else	{
			serial_printf("\nclear interrupt and alarm on the RTC device\n\n");
			data	= 0x00;
			i2c_write(I2C_ADDR_RTC, 0xe, 1, &data, 1);	/* disable interrupt */
			i2c_write(I2C_ADDR_RTC, 0x7, 1, &data, 1);	/* Alarm1 seconds */
			i2c_write(I2C_ADDR_RTC, 0x8, 1, &data, 1);	/* Alarm1 minutes */
			i2c_write(I2C_ADDR_RTC, 0x9, 1, &data, 1);	/* Alarm1 hours */
			i2c_write(I2C_ADDR_RTC, 0xa, 1, &data, 1);	/* Alarm1 day/date */
			i2c_write(I2C_ADDR_RTC, 0xf, 1, &data, 1);	/* clear interrupt */
		}
	}
	else if (strcmp(argv[1], "sfp") == 0)	{	/* SFP tx_dis test */
		port	= (int)simple_strtoul(argv[2], NULL, 10);
		/* release reset signal */
		cmic_base_p[CMIC_SOFT_RESET_REG]	= 0x1ffff;	/* for internal modules */
		pld_p[13]	= 0x0;	/* phy reset */

		switch (port)	{
		case 9:
			phy_addr	= 0x89;
			gpio_no		= 2;
			break;
		case 15:
			phy_addr	= 0xf;
			gpio_no		= 2;
			break;
		case 16:
			phy_addr	= 0x10;
			gpio_no		= 3;
			break;
		case 23:
			phy_addr	= 0x18;
			gpio_no		= 4;
			break;
		case 24:
			phy_addr	= 0x19;
			gpio_no		= 7;
			break;
		default:
			serial_printf("Wrong port number!\n");
			return	0;
		}

		serial_printf("Target port: %d\n", port);

		/* release TX_DIS for target port */
		serial_printf("\nSet low (enable) to TX_DIS (GPIO%d).\n", gpio_no);
		gpio_base_p[CMIC_GPIO_OE]	= gpio_base_p[CMIC_GPIO_OE] & ~(1<<gpio_no);
		gpio_base_p[CMIC_GPIO_OUT]	= gpio_base_p[CMIC_GPIO_OUT] & ~(1<<gpio_no);

		if (port != 9)	{
			serial_printf("Power up the SerDes.\n");
			/* disable copper I/F */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x00, 0x1940);
			/* set SGMII advertisement, disable HDX */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x04, 0x0020);
			/* set 2nd SerDes Auto detect, Invert SD, Auto-detect Medium */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1c, 0xfb27);
			/* Enable auto-detection between SGMII-slave and 1000BASE-X */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1c, 0xd401);
			/* write 1Ch, shadow 1Fh, select SGMII reg */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1c, 0xfc09);
			/* release Power Down, restart an on SGMII control reg */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x00, 0x1340);
			/* write 1Ch, shadow 1Fh, unselect SGMII reg */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1c, 0xfc08);
			udelay(500000);	/* wait for AN complete */
		}

		if (port != 9)	{
			serial_printf("Checking SD (RXLOS) status...\n");
			/* read 1Ch, shadow 1Fh */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1c, 0x7c00);
			value = (x210_mii_acc(X210MII_READ22, phy_addr, 0x1c, 0) & (1<<4));

			if (!value)	{
				serial_printf("\nFAIL\n");
				return	0;
			}
		}

		serial_printf("Checking link status...\n");
		if (port == 9)	{
			/* set 0x830 for BAR */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1f, 0x8300);
			value = (x210_mii_acc(X210MII_READ22, phy_addr, 0x14, 0) & (1<<1));
			/* reset BAR */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1f, 0x0000);
		}
		else	{
			/* read 1Ch, shadow 1Fh */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1c, 0x7c00);
			value = (x210_mii_acc(X210MII_READ22, phy_addr, 0x1c, 0) & (1<<6));
		}

		if (!value)	{
			serial_printf("\nFAIL\n");
			return	0;
		}

		/* set TX_DIS for target port */
		serial_printf("\nSet high (disable) to TX_DIS (GPIO%d).\n", gpio_no);
		gpio_base_p[CMIC_GPIO_OUT]	= gpio_base_p[CMIC_GPIO_OUT] | (1<<gpio_no);
		udelay(500000);	/* wait for link status to be down */

		if (port != 9)	{
			serial_printf("Checking SD (RXLOS) status...\n");
			/* read 1Ch, shadow 1Fh */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1c, 0x7c00);
			value = (x210_mii_acc(X210MII_READ22, phy_addr, 0x1c, 0) & (1<<4));

			if (value)	{
				serial_printf("\nFAIL\n");
				return	0;
			}
		}

		serial_printf("Checking link status...\n");
		if (port == 9)	{
			/* set 0x830 for BAR */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1f, 0x8300);
			value = (x210_mii_acc(X210MII_READ22, phy_addr, 0x14, 0) & (1<<1));
			/* reset BAR */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1f, 0x0000);
		}
		else	{
			/* read 1Ch, shadow 1Fh */
			x210_mii_acc(X210MII_WRITE22, phy_addr, 0x1c, 0x7c00);
			value = (x210_mii_acc(X210MII_READ22, phy_addr, 0x1c, 0) & (1<<6));
		}
		if (value)	{
			serial_printf("TX_DIS signal doesn't work on port%d.\n", port);
			return	0;
		}

		/* release TX_DIS for target port */
		cmic_base_p[CMIC_GPIO_OUT]	= cmic_base_p[CMIC_GPIO_OUT] & ~(1<<gpio_no);

		serial_printf("SFP TX_DIS test is completed.\nResult: PASS\n");
	}

	else	{
		cmd_usage(cmdtp);
		return	1;
	}

	return	0;
}

/* -------------------------------------------------------------------- */

U_BOOT_CMD(
	mfrtest, 3, 0,	do_mfr_test,
	"Perform functional test for factory inspection",
	"\nFor Auto standby mode\n"
	"mfrtest as 0       - clear alarm and interrupt on RTC\n"
	"mfrtest as [value] - set duration of standby and enter the mode\n"
	"\nFor SFP\n"
	"mfrtest sfp [port] - perform tx_dis function test"
);
