/* Driver for  Philips ip3902 ethernet hardware, found in PNX8335
   and probably other Philips chips.

   Written by Nikita V. Youshchenko <yoush@debian.org>.

   This software may be used and distributed according to the terms
   of the GNU General Public License, version 2.
*/

#include <common.h>

#if defined(CONFIG_IP3902) && (CONFIG_COMMANDS & CFG_CMD_NET)

#include <net.h>
#include <malloc.h>
#include <miiphy.h>
#include <asm/io.h>
#include <stb225_gpio.h>

/* Access to IP3902 registers */

/* Alcatel (Packet Engines) core registers */
#define MAC1_REG		0x000	/* R/W: MAC configuration register 1 */
#define MAC2_REG		0x004	/* R/W: MAC configuration register 2 */
#define IPGT_REG		0x008	/* R/W: Back-to-Back Inter-Packet-Gap register */
#define IPGR_REG		0x00c	/* R/W: Non Back-to-Back Inter-Packet-Gap register */
#define CLRT_REG		0x010	/* R/W: Collision window / Retry register */
#define MAXF_REG		0x014	/* R/W: Maximum Frame register */
#define SUPP_REG		0x018	/* R/W: PHY Support register */
#define TEST_REG		0x01C	/* R/W: Test register */
#define MCFG_REG		0x020	/* R/W: MII Mgmt Con???guration register */
#define MCMD_REG		0x024	/* R/W: MII Mgmt Command register */
#define MADR_REG		0x028	/* R/W: MII Mgmt Address register */
#define MWTD_REG		0x02C	/* WO:  MII Mgmt Write Data register */
#define MRDD_REG		0x030	/* RO:  MII Mgmt Read Data register */
#define MIND_REG		0x034	/* RO:  MII Mgmt Indicators register */
#define SA0_REG			0x040	/* R/W: Station Address 0 register */
#define SA1_REG			0x044	/* R/W: Station Address 1 register */
#define SA2_REG			0x048	/* R/W: Station Address 2 register */
/* Control registers */
#define COMMAND_REG		0x100	/* R/W: Command register */
#define STATUS_REG		0x104	/* RO:  Status register */
#define RX_DESC_REG		0x108	/* R/W: Receive descriptor base address register */
#define RX_STATUS_REG		0x10C	/* R/W: Receive status base address register */
#define RX_DESC_NUMBER_REG	0x110	/* R/W: Receive number of descriptors register */
#define RX_PRODUCE_INDEX_REG	0x114	/* RO:  Receive produce index register */
#define RX_CONSUME_INDEX_REG	0x118	/* R/W: Receive consume index register */
#define TX_DESC_REG		0x11C	/* R/W: Non real-time transmit descriptor base address register */
#define TX_STATUS_REG		0x120	/* R/W: Non real-time transmit status base address register */
#define TX_DESC_NUMBER_REG	0x124	/* R/W: Non real-time transmit number of descriptors register */
#define TX_PRODUCE_INDEX_REG	0x128	/* R/W: Non real-time transmit produce index register */
#define TX_CONSUME_INDEX_REG	0x12C	/* RO:  Non real-time transmit consume index register */
#define TX_RT_DESC_REG		0x130	/* R/W: Real-time transmit descriptor base address register */
#define TX_RT_STATUS_REG	0x134	/* R/W: Real-time transmit status base address register */
#define TX_RT_DESC_NUMBER_REG	0x138	/* R/W: Real-time transmit number of descriptors register */
#define TX_RT_PRODUCE_INDEX_REG	0x13C	/* R/W: Real-time transmit produce index register */
#define TX_RT_CONSUME_INDEX_REG	0x140	/* RO:  Real-time transmit consume index register */
#define QOS_TIMEOUT_REG		0x148	/* R/W: Transmit quality of service time-out register */
#define TSV0_REG		0x158	/* RO:  Transmit status vector 0 register */
#define TSV1_REG		0x15C	/* RO:  Transmit status vector 1 register */
#define RSV_REG			0x160	/* RO:  Receive status vector register */
#define FC_COUNTER_REG		0x170	/* R/W: Flow control counter register */
#define FC_STATUS_REG		0x174	/* RO:  Flow control status register */
/* Rx filter registers */
#define FILTER_CTRL_REG		0x200	/* R/W: Receive filter control register */
#define FILTER_WOL_STATUS_REG	0x204	/* RO:  Receive filter WoL status register */
#define FILTER_WOL_CLEAR_REG	0x208	/* WO:  Receive filter WoL clear register */
#define HASH_FILTER_L_REG	0x210	/* R/W: Hash filter table LSBs register */
#define HASH_FILTER_H_REG	0x214	/* R/W: Hash filter table MSBs register */
/* DVP Standard registers */
#define INT_STATUS_REG		0xFE0	/* RO:  Interrupt status register */
#define INT_ENABLE_REG		0xFE4	/* R/W: Interrupt enable register */
#define INT_CLEAR_REG		0xFE8	/* WO:  Interrupt clear register */
#define INT_SET_REG		0xFEC	/* WO:  Interrupt set register */
#define POWERDOWN_REG		0xFF4	/* R/W: Power-down register */
#define MODULE_ID_REG		0xFFC	/* RO:  Module ID register */

static inline unsigned long ip3902_read_reg(struct eth_device *dev, int reg)
{
	unsigned long ret = readl((void *)(dev->iobase + reg));
	return ret;
}

static inline void ip3902_write_reg(struct eth_device *dev, int reg, unsigned long val)
{
	writel(val, (void *)(dev->iobase + reg));
}

/* Bits for MAC1 register */
#define MAC1_SOFT_RESET		(1 << 15)
#define MAC1_TX_FLOW_CONTROL	(1 << 3)
#define MAC1_RX_FLOW_CONTROL	(1 << 2)
#define MAC1_RECEIVE_PASS_ALL	(1 << 1)
#define MAC1_RECEIVE_ENABLE	(1 << 0)

/* Bits for MAC2 register */
#define MAC2_AUTO_DETECT_PAD_ENABLE	(1 << 7)
#define MAC2_VLAN_PAD_ENABLE		(1 << 6)
#define MAC2_PAD_CRC_ENABLE		(1 << 5)
#define MAC2_CRC_ENABLE			(1 << 4)
#define MAC2_FULL_DUPLEX		(1 << 0)

#define INITIAL_MAC2	(MAC2_AUTO_DETECT_PAD_ENABLE | MAC2_VLAN_PAD_ENABLE | MAC2_PAD_CRC_ENABLE | MAC2_CRC_ENABLE)

/* Recommended values for IPGT register (see sec. 3.3.2.3 0f datasheet */
#define IPGT_FD_VALUE	0x15
#define IPGT_HD_VALUE	0x12

/* Bits for MCMD register */
#define MCMD_READ		(1 << 0)

/* Bits for MIND register */
#define MIND_NOT_VALID		(1 << 2)
#define MIND_BUSY		(1 << 0)

/* Bits for command register */
#define COMMAND_ENABLE_QOS	(1 << 11)
#define COMMAND_FULL_DUPLEX	(1 << 10)
#define COMMAND_RMII_MODE	(1 << 9)
#define COMMAND_TX_FLOW_CONTROL	(1 << 8)
#define COMMAND_PROMISC		(1 << 7)
#define COMMAND_ALLOW_SHORT	(1 << 6)
#define COMMAND_RX_RESET	(1 << 5)
#define COMMAND_TX_RESET	(1 << 4)
#define COMMAND_RESET		(1 << 3)
#define COMMAND_TX_RT_ENABLE	(1 << 2)
#define COMMAND_TX_ENABLE	(1 << 1)
#define COMMAND_RX_ENABLE	(1 << 0)

/* Bits for receive filter control register */
#define FILTER_ACCEPT_SELF		(1 << 5)
#define FILTER_ACCEPT_MCAST_HASH	(1 << 4)
#define FILTER_ACCEPT_UCAST_HASH	(1 << 3)
#define FILTER_ACCEPT_MCAST_ANY		(1 << 2)
#define FILTER_ACCEPT_BCAST_ANY		(1 << 1)
#define FILTER_ACCEPT_UCAST_ANY		(1 << 0)

/* Bit for POWERDOWN register */
#define POWERDOWN_VALUE		(1 << 31)


/* Since there is no need for Tx queue in u-boot, there is no need to have
 * more than 2 TX descriptors. We could have only one - but hardware does not
 * support it.
 * As for Rx, we should have several.
 */
#define RX_RING_SIZE	16

/* Related to incoming packet pool ... */
#define PACKET_SIZE_IN_POOL 0x600
#define PACKET_POOL_SIZE (PACKET_SIZE_IN_POOL * RX_RING_SIZE)
#define PACKET_POOL_OFFSET(i)	(2 + (i * PACKET_SIZE_IN_POOL))
/* For Max Frame Size register */
#define MAX_FRAME_SIZE (PACKET_SIZE_IN_POOL - 2) /* IP header alignment */

/* Bits for TX control */
#define TX_CONTROL_INT		(1 << 31)
#define TX_CONTROL_LAST		(1 << 30)
#define TX_CONTROL_CRC		(1 << 29)
#define TX_CONTROL_PAD		(1 << 28)
#define TX_CONTROL_HUGE		(1 << 27)
#define TX_CONTROL_OVERRIDE	(1 << 26)

/* The value we actually use */
#define TX_CONTROL (TX_CONTROL_LAST | TX_CONTROL_CRC | TX_CONTROL_PAD | TX_CONTROL_OVERRIDE)

/* Bits for RX status */
#define RX_STATUS_ERROR			(1 <<  7)
#define RX_STATUS_LAST_FRAG		(1 <<  6)
#define RX_STATUS_OVERRUN		(1 <<  4)
#define RX_STATUS_ALIGNMENT_ERROR	(1 <<  3)
#define RX_STATUS_RANGE_ERROR		(1 <<  2)
#define RX_STATUS_LENGTH_ERROR		(1 <<  1)
#define RX_STATUS_SYMBOL_ERROR		(1 <<  0)
#define RX_STATUS_CRC_ERROR		(1 << 15)
#define RX_STATUS_BROADCAST		(1 << 14)
#define RX_STATUS_MULTICAST		(1 << 13)
#define RX_STATUS_FAIL_FILTER		(1 << 12)
#define RX_STATUS_VLAN			(1 << 11)
#define RX_STATUS_CONTROL_FRAME		(1 << 10)
#define RX_STATUS_LENGTH(s)		((((s & 0xff000000) >> 24) | ((s & 0x00070000) >> 8)) + 1)

struct ip3902_descriptor {
	unsigned long address;
	unsigned long control;
};

struct ip3902_rx_status {
	unsigned long status;
	unsigned long hash_crc;
};

struct ip3902_private {
	struct ip3902_descriptor rx_desc[RX_RING_SIZE];
	struct ip3902_descriptor tx_desc[2];
	struct ip3902_rx_status rx_status[RX_RING_SIZE];
	unsigned long tx_status[2];

	int tx_index;
	int rx_last_consumed;
	uchar packet_pool[PACKET_POOL_SIZE];
	uchar *rx_packet[RX_RING_SIZE];
};
#define OTHER_TX(index) (1 - index)
#define NEXT_RX(index) (((index) == RX_RING_SIZE-1) ? 0 : (index + 1))


/* MII PHY support routines */

static inline void ip3902_write_madr_reg(struct eth_device *dev, unsigned char addr, unsigned char reg)
{
	unsigned long val = (((unsigned long)addr) << 8) | reg;
	ip3902_write_reg(dev, MADR_REG, val);
}

static inline int ip3902_wait_mdio_op_complete(struct eth_device *dev, unsigned long mask)
{
	int timeout = 10000;		/* to avoid hangup in case of unexpected badness ... */

	while (--timeout > 0)
	{
		if ((ip3902_read_reg(dev, MIND_REG) & mask) == 0)
		{
			return 0;
		}
		udelay(100);
	}

	return -1;
}

static int ip3902_mii_read_reg(struct eth_device *dev, unsigned char addr, unsigned char reg, unsigned short *value)
{
	ip3902_write_madr_reg(dev, addr, reg);
	ip3902_write_reg(dev, MCMD_REG, 0);
	ip3902_write_reg(dev, MCMD_REG, MCMD_READ);
	if (ip3902_wait_mdio_op_complete(dev, MIND_NOT_VALID | MIND_BUSY) < 0)
	{
		return -1;
	}
	else
	{
		*value = ip3902_read_reg(dev, MRDD_REG) & 0xffff;
		return 0;
	}
}

static int ip3902_mii_write_reg(struct eth_device *dev, unsigned char addr, unsigned char reg, unsigned short value)
{
	ip3902_write_madr_reg(dev, addr, reg);
	ip3902_write_reg(dev, MWTD_REG, value);
	return ip3902_wait_mdio_op_complete(dev, MIND_BUSY);
}

#if defined(CONFIG_MII) || (CONFIG_COMMANDS & CFG_CMD_MII)

/* Check if given phyaddr is valid, i.e. there is a PHY connected.
 * Do this by checking model value field from ID2 register.
 *
 * This code was taken from drivers/eepro100.c and edited appropriately.
 */
static struct eth_device* verify_phyaddr(char *devname, unsigned char addr)
{
	struct eth_device *dev;
	unsigned short value;
	unsigned char model;

	dev = eth_get_dev_by_name(devname);
	if (dev == NULL)
	{
		printf("%s: no such device\n", devname);
		return 0;
	}

	/* read id2 register */
	if (ip3902_mii_read_reg(dev, addr, PHY_PHYIDR2, &value) != 0)
	{
		printf("%s: mii read timeout!\n", devname);
		return 0;
	}

	/* get model */
	model = (unsigned char)((value >> 4) & 0x003f);

	if (model == 0)
	{
		printf("%s: no PHY at address %d\n", devname, addr);
		return 0;
	}

	return dev;
}

static int ip3902_miiphy_read (char *devname, unsigned char addr, unsigned char reg, unsigned short *value)
{
	struct eth_device *dev = verify_phyaddr(devname, addr);
	if (!dev)
	{
		return -1;
	}

	if (ip3902_mii_read_reg(dev, addr, reg, value) < 0)
	{
		printf("%s: mii read timeout!\n", devname);
		return -1;
	}

	return 0;
}

static int ip3902_miiphy_write (char *devname, unsigned char addr,
                unsigned char reg, unsigned short value)
{
	struct eth_device *dev = verify_phyaddr(devname, addr);
	if (!dev)
	{
		return -1;
	}

	if (ip3902_mii_write_reg(dev, addr, reg, value) < 0)
	{
		printf("%s: mii write timeout!\n", devname);
		return -1;
	}

	return 0;
}

#endif

/* eth_device methods */

static int ip3902_init(struct eth_device *dev, bd_t *bd)
{
	struct ip3902_private *tp = (struct ip3902_private *)(dev->priv);
	int speed, duplex, addr, i;
	unsigned short stat;
	unsigned long val;

	/* Move device out of reset/poweroff */
	ip3902_write_reg(dev, POWERDOWN_REG, 0);
	ip3902_write_reg(dev, MCFG_REG, 0x1c);
	ip3902_write_reg(dev, MAC1_REG, MAC1_SOFT_RESET);
	ip3902_write_reg(dev, MAC1_REG, 0);
	ip3902_write_reg(dev, MAC2_REG, INITIAL_MAC2);

	/* Search for PHY */
	for (addr = 0; addr < 32; addr++)
	{
		if (ip3902_mii_read_reg(dev, addr, PHY_BMSR, &stat) == 0 &&
		    stat != 0 && stat != 0xffff)
			break;
	}
	if (addr == 32)
	{
		printf("ip3902: could not locate PHY\n");
		return 0;
	}

	/* High-level reset */
	ip3902_write_reg(dev, COMMAND_REG, 0);
	ip3902_write_reg(dev, COMMAND_REG, COMMAND_RESET | COMMAND_TX_RESET | COMMAND_RX_RESET);
	ip3902_write_reg(dev, COMMAND_REG, 0);

	/* Get speed and duplex settings. FIXME. */
	speed = miiphy_speed(dev->name, addr);
	duplex = miiphy_duplex(dev->name, addr);

	/* ... and configure appropriately */
	if (duplex == FULL)
	{
		ip3902_write_reg(dev, MAC2_REG,	INITIAL_MAC2 | MAC2_FULL_DUPLEX);
		ip3902_write_reg(dev, IPGT_REG, IPGT_FD_VALUE);
	}
	else
	{
		ip3902_write_reg(dev, IPGT_REG, IPGT_HD_VALUE);
	}

	/* Set hardware address */
	ip3902_write_reg(dev, SA0_REG, (dev->enetaddr[0]) << 8 | dev->enetaddr[1]);
	ip3902_write_reg(dev, SA1_REG, (dev->enetaddr[2]) << 8 | dev->enetaddr[3]);
	ip3902_write_reg(dev, SA2_REG, (dev->enetaddr[4]) << 8 | dev->enetaddr[5]);

	/* Setup Rx filter: accept broadcast and frames for self */
	ip3902_write_reg(dev, HASH_FILTER_L_REG, 0xffffffff);
	ip3902_write_reg(dev, HASH_FILTER_H_REG, 0x80000000);
	ip3902_write_reg(dev, FILTER_CTRL_REG, FILTER_ACCEPT_SELF | FILTER_ACCEPT_BCAST_ANY | FILTER_ACCEPT_MCAST_HASH);

	/* Configure Tx */
	ip3902_write_reg(dev, TX_DESC_REG, virt_to_phys(tp->tx_desc));
	ip3902_write_reg(dev, TX_STATUS_REG, virt_to_phys(tp->tx_status));
	ip3902_write_reg(dev, TX_DESC_NUMBER_REG, 1);
	ip3902_write_reg(dev, TX_PRODUCE_INDEX_REG, 0);
	ip3902_write_reg(dev, TX_CONSUME_INDEX_REG, 0);
	tp->tx_index = 0;

	/* Configure Rx */
	for (i = 0; i < RX_RING_SIZE; i++)
	{
		struct ip3902_descriptor *rx_desc = (struct ip3902_descriptor*)(virt_to_phys(&tp->rx_desc[i]) | KSEG1);
		tp->rx_packet[i] = &(tp->packet_pool[PACKET_POOL_OFFSET(i)]);
		rx_desc->address = virt_to_phys(tp->rx_packet[i]);
		rx_desc->control = MAX_FRAME_SIZE - 1;
	}
	ip3902_write_reg(dev, RX_DESC_REG, virt_to_phys(tp->rx_desc));
	ip3902_write_reg(dev, RX_STATUS_REG, virt_to_phys(tp->rx_status));
	ip3902_write_reg(dev, RX_DESC_NUMBER_REG, RX_RING_SIZE - 1);
	ip3902_write_reg(dev, RX_PRODUCE_INDEX_REG, 0);
	ip3902_write_reg(dev, RX_CONSUME_INDEX_REG, 0);
	ip3902_write_reg(dev, MAXF_REG, MAX_FRAME_SIZE);
	tp->rx_last_consumed = 0;

	/* Enable Tx and Rx at high level */
	val = COMMAND_TX_ENABLE | COMMAND_RX_ENABLE | COMMAND_ALLOW_SHORT | COMMAND_PROMISC;
	if (duplex == FULL)
	{
		val |= COMMAND_FULL_DUPLEX;
	}
	ip3902_write_reg(dev, COMMAND_REG, val);

	/* ... and at low level */
	ip3902_write_reg(dev, MAC1_REG, MAC1_RECEIVE_ENABLE);

	printf("ip3902: PHY at %d, %s Mbps, %s duplex\n", addr,
			((speed == _100BASET) ? "100" : "10"),
			((duplex == FULL) ? "full" : "half"));
	return 1;
}

static int ip3902_send(struct eth_device *dev, volatile void *packet, int len)
{
	struct ip3902_private *tp = (struct ip3902_private *)(dev->priv);
	int p = tp->tx_index, c;
	int i;

	/* If descriptor is not free yet, wait for it up to 500 milliseconds */
	for (i = 5000; i > 0; i--)
	{
		if ((c = ip3902_read_reg(dev, TX_CONSUME_INDEX_REG)) == p)
		{
			break;
		}
		udelay(100);
	}
	if (c != p)
	{
		printf("ip3902: Tx timeout\n");
		return 0;
	}

	/* Put a new descriptor */
	{
		struct ip3902_descriptor *tx_desc = (struct ip3902_descriptor*)(virt_to_phys(&tp->tx_desc[p]) | KSEG1);

		tx_desc->address = virt_to_phys(packet);
		tx_desc->control = (len - 1) | TX_CONTROL;
	}
	tp->tx_index = p = OTHER_TX(p);
	ip3902_write_reg(dev, TX_PRODUCE_INDEX_REG, p);

	return len;
}

static int ip3902_recv(struct eth_device *dev)
{
	struct ip3902_private *tp = (struct ip3902_private *)(dev->priv);
	int p, length = 0;

	for (p = tp->rx_last_consumed;
	     p != ip3902_read_reg(dev, RX_PRODUCE_INDEX_REG);
	     p = NEXT_RX(p))
	{
		unsigned long *pstatus = (unsigned long*)(virt_to_phys(&tp->rx_status[p].status) | KSEG1);
		unsigned long status   = *pstatus;

		if (status & RX_STATUS_FAIL_FILTER)
		{
			continue;
		}
		if (!(status & RX_STATUS_LAST_FRAG) ||
		    (status & (RX_STATUS_OVERRUN | RX_STATUS_ALIGNMENT_ERROR | RX_STATUS_LENGTH_ERROR | RX_STATUS_CRC_ERROR)))
		{
			printf("ip3902: Rx error, status %08x\n", status);
			continue;
		}
		length = RX_STATUS_LENGTH(status);
		NetReceive ((unsigned char *) (virt_to_phys (tp->rx_packet[p]) | KSEG1), length);
	}
	tp->rx_last_consumed = p;
	ip3902_write_reg(dev, RX_CONSUME_INDEX_REG, p);

	return length;
}

static void ip3902_halt(struct eth_device *dev)
{
	/* Put low-level hardware into reset, and high-level into poweroff */
	ip3902_write_reg(dev, COMMAND_REG, 0);
	ip3902_write_reg(dev, COMMAND_REG, COMMAND_RESET | COMMAND_TX_RESET | COMMAND_RX_RESET);
	ip3902_write_reg(dev, MAC1_REG, MAC1_SOFT_RESET);
	ip3902_write_reg(dev, POWERDOWN_REG, POWERDOWN_VALUE);
	/* not bothering to free off any malloced memory as the kernel will take over in a moment */
}

int ip3902_initialize(bd_t *bd)
{
	struct eth_device *dev;
	struct ip3902_private *tp;

	gpio_set_as_function (GPIO_ETH_COL);
	gpio_set_as_function (GPIO_ETH_RXD2);
	gpio_set_as_function (GPIO_ETH_RXD3);
	gpio_set_as_function (GPIO_ETH_RXDV);
	gpio_set_as_function (GPIO_ETH_TXD2);
	gpio_set_as_function (GPIO_ETH_TXD3);
#if defined (GPIO_EXT_PHY_RESET) && (GPIO_EXT_PHY_RESET == GPIO_ETH_TXER)
	/*
	   GPIO_ETH_TXER re-assigned as the reset for the external Phy. Pin has
	   been setup as an output in early board init so leave it alone here.
	*/
#else
	gpio_set_as_function (GPIO_ETH_TXER);
#endif

	dev = (struct eth_device *) malloc(sizeof(*dev));
	strcpy(dev->name, "ip3902");
	dev->iobase = CFG_IP3902_IOBASE;
	memcpy(dev->enetaddr, bd->bi_enetaddr, 6);

	tp = (struct ip3902_private *) malloc(sizeof(*tp));
	memset(tp, 0, sizeof(*tp));
	dev->priv = tp;

	dev->init = ip3902_init;
	dev->halt = ip3902_halt;
	dev->send = ip3902_send;
	dev->recv = ip3902_recv;
	eth_register(dev);

#if defined(CONFIG_MII) || (CONFIG_COMMANDS & CFG_CMD_MII)
	miiphy_register(dev->name, ip3902_miiphy_read, ip3902_miiphy_write);
#endif

	return 1;
}

#endif
