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

#define DX160_PHY_MII_REG_CONTROL		0
#define DX160_PHY_MII_REG_STATUS		1
#define DX160_PHY_MII_REG_PHYID_1		2
#define DX160_PHY_MII_REG_PHYID_2		3
#define DX160_PHY_MII_REG_AUTONEG_ADV		4
#define DX160_PHY_MII_REG_AUTONEG_LPA		5
#define DX160_PHY_MII_REG_AUTONEG_EXT		6
#define DX160_PHY_MII_REG_AUTONEG_1000BT	9
#define DX160_PHY_MII_REG_SPECIFIC		16
#define DX160_PHY_MII_REG_SPECIFIC_STATUS	17
#define DX160_PHY_MII_REG_SPECIFIC_CTRL		20
#define DX160_PHY_MII_REG_EXTEND_ADDR		22
#define DX160_PHY_MII_REG_EXTEXT_ADDR		29

#define DX160_PHY_CTRL_SW_RESET			0x8000
#define DX160_PHY_CTRL_LOOPBACK			0x4000
#define DX160_PHY_CTRL_SPEED			0x2000
#define DX160_PHY_CTRL_AUTO_NEG_EN		0x1000
#define DX160_PHY_CTRL_PWR_DOWN			0x0800
#define DX160_PHY_CTRL_ISOLATE_MASK		0x0400
#define DX160_PHY_CTRL_RESTART_AUTO_NEG		0x0200
#define DX160_PHY_CTRL_DUPLEX			0x0100
#define DX160_PHY_CTRL_COL_TEST_MASK		0x0080
#define DX160_PHY_CTRL_SPEED_MSB		0x0040
#define DX160_PHY_MASK_TO_0			0x003F
#define DX160_PHY_CTRL_SPEED_MASK		0x2040

#define M85XXT_MIIM_CONTROL_INIT		0x1140

static const struct dx160_config_reg ar570_dx160_config[] = {
	/* Global */
	{0x00000000, 0x0000c1fb, 0x00004002},
	{0x00000004, 0xffffffff, 0x604ce180},

	{0x000000a4, 0x00000001, 0x00000000},
	{0x000000a0, 0xe403be01, 0x24030a00},
	{0x000000a4, 0x0000000c, 0x0000000c},
	{0x000000a4, 0x00000001, 0x00000001},

	/* No interrupts */
	{0x03000044, 0xffffffff, 0x00000000},

	/* Buffer Management */
	{0x03000000, 0x7fffffff, 0x7fccd172},
	{0x03000020, 0x3fffffff, 0x07f1980e},
	{0x03000024, 0x3fffffff, 0x07f1980e},
	{0x03000030, 0x8000ffff, 0x0000ffe0},

	/* Network Ports */
	{0x04004030, 0xffffffff, 0x0c520c41},
	{0x04804030, 0xffffffff, 0x00000000},
	{0x05004030, 0xffffffff, 0x00000107},
	{0x05804030, 0xffffffff, 0x00000000},

	{0x04000000, 0x00000001, 0x00000000},
	{0x04000100, 0x00000001, 0x00000000},
	{0x04000200, 0x00000001, 0x00000000},
	{0x04000300, 0x00000001, 0x00000000},
	{0x04000400, 0x0003fe01, 0x00030a01},
	{0x04000500, 0x0003fe01, 0x00030a01},
	{0x04800000, 0x00000001, 0x00000000},
	{0x04800100, 0x00000001, 0x00000000},
	{0x04800200, 0x00000001, 0x00000000},
	{0x04800300, 0x00000001, 0x00000000},
	{0x04800400, 0x00000001, 0x00000000},
	{0x04800500, 0x00000001, 0x00000000},
	{0x05000000, 0x0003fe01, 0x00034a01},
	{0x05000100, 0x0003fe01, 0x00034a01},
	{0x05000200, 0x00000001, 0x00000000},
	{0x05000300, 0x00000001, 0x00000000},

	{0x04000008, 0x00000003, 0x00000001},
	{0x04000108, 0x00000003, 0x00000001},
	{0x04000208, 0x00000003, 0x00000001},
	{0x04000308, 0x00000003, 0x00000001},
	{0x04000408, 0x00003fff, 0x000033fc},
	{0x04000508, 0x00003fff, 0x000033fc},
	{0x04800008, 0x00000003, 0x00000001},
	{0x04800108, 0x00000003, 0x00000001},
	{0x04800208, 0x00000003, 0x00000001},
	{0x04800308, 0x00000003, 0x00000001},
	{0x04800408, 0x00000003, 0x00000001},
	{0x04800508, 0x00000003, 0x00000001},
	{0x05000008, 0x00003fff, 0x00003548},
	{0x05000108, 0x00003fff, 0x00003548},
	{0x05000208, 0x00000003, 0x00000001},
	{0x05000308, 0x00000003, 0x00000001},

	/* SERDES amp. (600mv) + reset */
	{0x04000010, 0x0007ffff, 0x0000800c},
	{0x04000110, 0x0007ffff, 0x0000800c},
	{0x04000210, 0x0007ffff, 0x0000800c},
	{0x04000310, 0x0007ffff, 0x0000800c},
	{0x04000410, 0x0007ffff, 0x0000f8cc},
	{0x04000510, 0x0007ffff, 0x0000f8cc},
	{0x04800010, 0x0007ffff, 0x0000800c},
	{0x04800110, 0x0007ffff, 0x0000800c},
	{0x04800210, 0x0007ffff, 0x0000800c},
	{0x04800310, 0x0007ffff, 0x0000800c},
	{0x04800410, 0x0007ffff, 0x0000800c},
	{0x04800510, 0x0007ffff, 0x0000800c},
	{0x05000010, 0x0007ffff, 0x0000f8cc},
	{0x05000110, 0x0007ffff, 0x0000f8cc},
	{0x05000210, 0x0007ffff, 0x0000800c},
	{0x05000310, 0x0007ffff, 0x0000800c},

	/* un-reset SERDES */
	{0x04000410, 0x00008000, 0x00000000},
	{0x04000510, 0x00008000, 0x00000000},
	{0x05000010, 0x00008000, 0x00000000},
	{0x05000110, 0x00008000, 0x00000000},

	/* Bridge */
	{0x02018000, 0x44000000, 0x44000000},

	{0x02040000, 0x01140006, 0x00100000},
	{0x02040028, 0x00ffffff, 0x00000000},
	{0x020400a8, 0x0fff0000, 0x0ffe0000},
	{0x020400ac, 0x0fff0000, 0x0ffe0000},

	{0x06000000, 0x00000290, 0x00000010},

	/* Egress / TxQueue / VLT */
	{0x01800004, 0xfffff9f0, 0x0392f9f0},
	{0x01800500, 0xffffffff, 0x02020202},
	{0x01810500, 0xffffffff, 0x02020202},

	/* Buffer Memory */
	{0x07800004, 0x01000000, 0x00000000},

	{0x07800004, 0x02000000, 0x02000000},

	/* Automedia */
	{0x04004034, 0x00000170, 0x00000170},

	/* VID entry 0 enable learning, ports 4 & 12 untagged */
	{0x0a002000, 0xff1fb013, 0x00000003},
	{0x0a002004, 0xffffffff, 0x00010001},
	{0x0a002008, 0xffffffff, 0x00000000},

	/* VID entry 1 enable learning, ports 5 & 13 untagged */
	{0x0a002010, 0xff1fb013, 0x00000003},
	{0x0a002014, 0xffffffff, 0x00040004},
	{0x0a002018, 0xffffffff, 0x00000000},

	/* VLAN Translation table entry 1.
	 * Translate VLAN tag 3 to VID 1 and VLAN tag 2 to VID 0 */
	{0x0a000004, 0x0003ffff, 0x00020300},

	/* Ports 4 & 5 VID register, add tags 2 & 3 */
	{0x02004004, 0x0fff0fff, 0x00030002},

	/* Ports 12 & 13 VID register, add tags 2 & 3 */
	{0x0200c004, 0x0fff0fff, 0x00030002},

	/* Bridge Ports 4, 5, 12 & 13 Control register.
	 * set port based VLAN enable bit */
	{0x02004000, 0x00040000, 0x00040000},
	{0x02005000, 0x00040000, 0x00040000},
	{0x0200c000, 0x00040000, 0x00040000},
	{0x0200d000, 0x00040000, 0x00040000},

	/* LED Interface0 Control Register */
	{0x04004100, 0xffffffff, 0x34a45300},

	/* Global */
	{0x00000000, 0x00000001, 0x00000001},

	{0x000000a4, 0x00000001, 0x00000000},
	{0x000000a4, 0x00000001, 0x00000001},

	/* A3 errata fixes */
	{0x01850000, 0x08000000, 0x08000000},

	{0x02040000, 0x00004000, 0x00004000},

	{0x04004060, 0x00000004, 0x00000004},
	{0x04804060, 0x00000004, 0x00000004},
	{0x05004060, 0x00000004, 0x00000004},
	{0x05804060, 0x00000004, 0x00000004},

	{0x00000054, 0x00000004, 0x00000004},

	/* Allow forwarding on ports 13,12,5,4 */
	{0x0A006000, 0xFFFFFFFF, 0x5f555f55},

	/* End of config marker */
	{0, 0, 0}
};

static u32 swkDX160PhyMarvell88E1112Init(char *devname, int smi_addr, u32 port)
{
	u16 regSpecValue;
	u16 page;
	u16 value;
	u16 macSpec;
	u32 reg;

	/* disable automatic reading of over the SMI bus */
	dx160_reg_read(devname, smi_addr, (0x04004034 + (port / 6) * 0x800000),
		       &reg);
	reg |= 0x40;
	dx160_reg_write(devname, smi_addr, (0x04004034 + (port / 6) * 0x800000),
			reg);

	/* set the port into autonegotiate mode */
	dx160_mii_write(devname, smi_addr, port, DX160_PHY_MII_REG_CONTROL,
			M85XXT_MIIM_CONTROL_INIT);

	/* implement Errata C1 to stop potential SERDES lockups */
	dx160_mii_write(devname, smi_addr, port,
			DX160_PHY_MII_REG_EXTEXT_ADDR, 8);
	dx160_mii_read(devname, smi_addr, port, 30, &value);
	dx160_mii_write(devname, smi_addr, port, 30, value & 0xfffc);

	dx160_mii_write(devname, smi_addr, port,
			DX160_PHY_MII_REG_EXTEXT_ADDR, 12);
	dx160_mii_read(devname, smi_addr, port, 30, &value);
	dx160_mii_write(devname, smi_addr, port, 30, value & 0xfffc);

	/* wait another ms */
	udelay(1000);

	/* reset the SGMII MAC interface SERDES block */
	dx160_mii_write(devname, smi_addr, port,
			DX160_PHY_MII_REG_EXTEXT_ADDR, 0x17);
	dx160_mii_write(devname, smi_addr, port, 30, 0x4008);
	dx160_mii_write(devname, smi_addr, port, 30, 0x0000);

	/* reset the Fiber interface SERDES block */
	dx160_mii_write(devname, smi_addr, port,
			DX160_PHY_MII_REG_EXTEXT_ADDR, 0x18);
	dx160_mii_write(devname, smi_addr, port, 30, 0x00ca);
	dx160_mii_write(devname, smi_addr, port, 0x1e, 0x0000);

	/* wait 2 ms */
	udelay(2000);

	/* set off automatic page selection,
	   by reading register 22 and setting page 0 */
	dx160_mii_read(devname, smi_addr, port,
		       DX160_PHY_MII_REG_EXTEND_ADDR, &page);
	dx160_mii_write(devname, smi_addr, port,
			DX160_PHY_MII_REG_EXTEND_ADDR, page & 0x7f00);
	dx160_mii_read(devname, smi_addr, port, DX160_PHY_MII_REG_SPECIFIC,
		       &regSpecValue);
	dx160_mii_write(devname, smi_addr, port, DX160_PHY_MII_REG_SPECIFIC,
			regSpecValue | 0x800);

	/* do a phy reset to make changes active */
	dx160_mii_write(devname, smi_addr, port, DX160_PHY_MII_REG_CONTROL,
			DX160_PHY_CTRL_SW_RESET | M85XXT_MIIM_CONTROL_INIT);

	/* in SGMII mode we need to configure both sides, the external copper
	   and the internal SERDES/fiber.
	   in particular we must stop in-band autonegotiation with the DX160 */
	/* set default setup for the FIBER side, autonegotiation is enabled.*/
	dx160_mii_write(devname, smi_addr, port,
			DX160_PHY_MII_REG_EXTEND_ADDR,
			((page & 0x7f00) | 1));
	dx160_mii_read(devname, smi_addr, port, 26, &macSpec);
	dx160_mii_write(devname, smi_addr, port, 26, macSpec & 0x7fff);
	dx160_mii_write(devname, smi_addr, port, DX160_PHY_MII_REG_CONTROL,
			DX160_PHY_CTRL_AUTO_NEG_EN | DX160_PHY_CTRL_DUPLEX
			| DX160_PHY_CTRL_SPEED_MSB);
	dx160_mii_write(devname, smi_addr, port, DX160_PHY_MII_REG_CONTROL,
			DX160_PHY_CTRL_AUTO_NEG_EN | DX160_PHY_CTRL_DUPLEX
			| DX160_PHY_CTRL_SPEED_MSB |
			DX160_PHY_CTRL_SW_RESET);

	/* set the signal detect pin sensing to low for SFP ports */
	dx160_mii_write(devname, smi_addr, port, 16, 0x200);

	/* set the default setup for the SGMII side, no autonegotiation */
	dx160_mii_write(devname, smi_addr, port,
			DX160_PHY_MII_REG_EXTEND_ADDR,
			((page & 0x7f00) | 2));

	/* bypass autoneg on SGMII MAC interface */
	dx160_mii_read(devname, smi_addr, port, 26, &macSpec);
	dx160_mii_write(devname, smi_addr, port, 26, macSpec | 0x8000);
	dx160_mii_write(devname, smi_addr, port, DX160_PHY_MII_REG_CONTROL,
			DX160_PHY_CTRL_SPEED_MSB);
	dx160_mii_write(devname, smi_addr, port, DX160_PHY_MII_REG_CONTROL,
			DX160_PHY_CTRL_SPEED_MSB |
			DX160_PHY_CTRL_SW_RESET);

	/* set the prefered media to fiber  2_16:11:10 */
	dx160_mii_read(devname, smi_addr, port, DX160_PHY_MII_REG_SPECIFIC,
		       &macSpec);
	dx160_mii_write(devname, smi_addr, port, DX160_PHY_MII_REG_SPECIFIC,
			macSpec | 0x400);
	dx160_mii_write(devname, smi_addr, port,
			DX160_PHY_MII_REG_EXTEND_ADDR, page & 0x7f00);

	/* enable automatic reading of over the SMI bus */
	dx160_reg_read(devname, smi_addr, (0x04004034 + (port / 6) * 0x800000),
		       &reg);
	reg &= ~0x40;
	dx160_reg_write(devname, smi_addr, (0x04004034 + (port / 6) * 0x800000),
			reg);

	return 0;
}

static u32 swkDX160PhyMarvell88E1143Init(char *devname, int smi_addr)
{
	u16 phyValue;
	u16 ctrlValue;
	u16 page;

/*  set off automatic page selection, by reading register 22 and setting page 0
*/
	miiphy_read(devname, smi_addr, DX160_PHY_MII_REG_EXTEND_ADDR, &page);

/*  select page 1
*/
	page &= 0xff00;
	page |= 1;
	miiphy_write(devname, smi_addr, DX160_PHY_MII_REG_EXTEND_ADDR, page);

/*  set the default setup for the Fiber side, no autonegotiation
*/
	ctrlValue = DX160_PHY_CTRL_DUPLEX | DX160_PHY_CTRL_SPEED_MSB;
	miiphy_write(devname, smi_addr, DX160_PHY_MII_REG_CONTROL,
		     ctrlValue);

/*  set on RGMII transmit and receive delay	timing
*/
	miiphy_read(devname, smi_addr, DX160_PHY_MII_REG_SPECIFIC_CTRL,
		    &phyValue);
	phyValue |= 0xf2;
	miiphy_write(devname, smi_addr, DX160_PHY_MII_REG_SPECIFIC_CTRL,
		     phyValue);

	ctrlValue |= DX160_PHY_CTRL_SW_RESET;
	miiphy_write(devname, smi_addr, DX160_PHY_MII_REG_CONTROL,
		     ctrlValue);

	page &= 0xff00;
	miiphy_write(devname, smi_addr, DX160_PHY_MII_REG_EXTEND_ADDR, page);

	return 0;
}

int ar570s_dx160_setup(void)
{
	/* Setup switch chip */
	dx160_config("wan0", 0x10, ar570_dx160_config);

	/* Assert EN_DEV to enable operation */
	*((char *)(CONFIG_SYS_CPLD_BASE + 0x10)) = 0x02;

	/* Init external PHYs for eth0 and eth1 */
	swkDX160PhyMarvell88E1112Init("wan0", 0x10, 5);
	swkDX160PhyMarvell88E1112Init("wan0", 0x10, 6);

	/* Init internal PHYs for connection to switch */
	swkDX160PhyMarvell88E1143Init("wan0", 7);
	swkDX160PhyMarvell88E1143Init("wan0", 8);

	/* Port LED control DX160LEDControlRegister) */
	*((char *)(CONFIG_SYS_CPLD_BASE + 0x15)) = 0x01;

	return 0;
}
