/*
 * cs8900.c
 *
 * Cirrus Logic Crystal CS89X0 Ethernet driver for barebox
 * Copyright (C) 2009 Ivo Clarysse <ivo.clarysse@gmail.com>
 *
 * Based on cs8900.c from barebox mainline,
 *   (C) 2003 Wolfgang Denk, wd@denx.de
 *   (C) Copyright 2002 Sysgo Real-Time Solutions, GmbH <www.elinos.com>
 *   Marius Groeger <mgroeger@sysgo.de>
 *   Copyright (C) 1999 Ben Williamson <benw@pobox.com>
 *
 *      This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version 2
 *      of the License, or (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 */

#include <common.h>
#include <driver.h>
#include <init.h>
#include <net.h>
#include <malloc.h>
#include <clock.h>
#include <io.h>
#include <errno.h>

/* I/O ports for I/O Space operation */
#define CS8900_RTDATA0	0x0000	/* Receive/Transmit Data (Port 0) */
#define CS8900_RTDATA1	0x0002	/* Receive/Transmit Data (Port 1) */
#define CS8900_TXCMD	0x0004	/* Transmit Command */
#define CS8900_TXLEN	0x0006	/* Transmit Length */
#define CS8900_ISQ	0x0008	/* Interrupt Status Queue */
#define CS8900_PP_PTR	0x000a	/* PacketPage Pointer */
#define CS8900_PP_DATA0	0x000c	/* PacketPage Data (Port 0) */
#define CS8900_PP_DATA1	0x000e	/* PacketPage Data (Port 1) */

/* PacketPage registers */

/* Bus Interface registers */
#define PP_BI_VID	0x0000	/* Vendor (EISA registration #) */
#define PP_BI_PID	0x0002	/* Product ID */
#define PP_BI_IOBASE	0x0020	/* I/O Base Address */
#define PP_BI_IRQ	0x0022	/* Interrupt Number */
#define PP_BI_DMACH	0x0024	/* DMA Channel Number */
#define PP_BI_DMASOF	0x0026	/* DMA Start of Frame */
#define PP_BI_DMAFC	0x0028	/* DMA Frame Count (12bit) */
#define PP_BI_RXDMAC	0x002A	/* RxDMA Byte Count */
#define PP_BI_MBAR	0x002C	/* Memory Base Address Register (20bit) */
#define PP_BI_BPBA	0x0030	/* Boot PROM Base Address (32bit) */
#define PP_BI_BPAM	0x0034	/* Boot PROM Address Mark (32bit) */
#define PP_BI_EE_CMD	0x0040	/* EEPROM Command */
#define PP_BI_EE_DAT	0x0042	/* EEPROM Data */
#define PP_BI_RFBC	0x0050	/* Received Frame Byte Counter */

/* Status and Control registers */
#define PP_REG_ISQ	0x0120	/* Register 0: ISQ */
#define PP_REG_01	0x0100	/* Register 1: -reserved- */
#define PP_REG_02	0x0122	/* Register 2: -reserved- */
#define PP_REG_RXCFG	0x0102	/* Register 3: RxCFG */
#define PP_REG_RXEVENT	0x0124	/* Register 4: RxEvent/RxEventalt */
#define PP_REG_RXCTL	0x0104	/* Register 5: RxCTL */
#define PP_REG_06	0x0126	/* Register 6: -reserved- */
#define PP_REG_TXCFG	0x0106	/* Register 7: TxCFG */
#define PP_REG_TXEVENT	0x0128	/* Register 8: TxEvent */
#define PP_REG_TXCMD	0x0108	/* Register 9: TxCmd (Transmit Command Status) */
#define PP_REG_0A	0x012A	/* Register A: -reserved- */
#define PP_REG_BUFCFG	0x010A	/* Register B: BufCFG */
#define PP_REG_BUFEVENT	0x012C	/* Register C: BufEvent */
#define PP_REG_0C	0x010C	/* Register D: -reserved- */
#define PP_REG_0E	0x012E	/* Register E: -reserved- */
#define PP_REG_0F	0x010E	/* Register F: -reserved- */
#define PP_REG_RXMISS	0x0130	/* Register 10: RxMISS */
#define PP_REG_11	0x0110	/* Register 11: -reserved- */
#define PP_REG_TXCOL	0x0132	/* Register 12: TxCOL */
#define PP_REG_LINECTL	0x0112	/* Register 13: LineCTL */
#define PP_REG_LINEST	0x0134	/* Register 14: LineST */
#define PP_REG_SELFCTL	0x0114	/* Register 15: SelfCTL */
#define PP_REG_SELFST	0x0136	/* Register 16: SelfST */
#define PP_REG_BUSCTL	0x0116	/* Register 17: BusCTL */
#define PP_REG_BUSST	0x0138	/* Register 18: BusST */
#define PP_REG_TESTCTL	0x0118	/* Register 19: TestCTL */
#define PP_REG_1A	0x013A	/* Register 1A: -reserved- */
#define PP_REG_1B	0x011A	/* Register 1B: -reserved- */
#define PP_REG_TDR	0x013C	/* Register 1C: TDR */
#define PP_REG_1D	0x011C	/* Register 1D: -reserved- */
#define PP_REG_1E	0x013E	/* Register 1E: -reserved- */
#define PP_REG_1F	0x011E	/* Register 1F: -reserved- */

/* Initiate Transmit registers */
#define PP_IT_TXCMD	0x0144	/* Transmit Command */
#define PP_IT_TXLEN	0x0146	/* Transmit Length */

/* Address Filter registers */
#define PP_AF_LAF	0x0150	/* Logical Address Filter */
#define PP_AF_IA	0x0158	/* Individual address (IEEE address) */

/* Frame Location */
#define PP_FL_RXSTATUS	0x0400	/* RXStatus */
#define PP_FL_RXLENGTH	0x0402	/* RxLength */
#define PP_FL_RXLOC	0x0404	/* Receive Frame Location */
#define PP_FL_TXLOC	0x0A00	/* Transmit Frame Location */

/* Register bit masks */
#define RXEVENT_RXOK		0x0100
#define RXCTL_RXOKA		0x0100
#define RXCTL_MULTICASTA	0x0200
#define RXCTL_INDIVIDUALA	0x0400
#define RXCTL_BROADCASTA	0x0800
#define TXCMD_TXSTART_5		0x0000	/* Start Tx after 5 bytes */
#define TXCMD_TXSTART_381	0x0040	/* Start Tx after 381 bytes */
#define TXCMD_TXSTART_1021	0x0080	/* Start Tx after 1021 bytes */
#define TXCMD_TXSTART_FULL	0x00C0	/* Start Tx after all bytes loaded */
#define LINECTL_SERRXON		0x0040
#define LINECTL_SERTXON		0x0080
#define LINEST_LINKOK		0x0080
#define LINEST_AUI		0x0100
#define LINEST_10BT		0x0200
#define LINEST_POLARITYOK	0x1000
#define LINEST_CRS		0x4000
#define SELFCTL_RESET		0x0040
#define SELFST_33VACTIVE	0x0040
#define SELFST_INITD		0x0080
#define SELFST_SIBUSY		0x0100
#define SELFST_EEPROMP		0x0200
#define SELFST_EEPROMOK		0x0400
#define SELFST_ELPRESENT	0x0800
#define SELFST_EESIZE		0x1000
#define BUSST_TXBIDERR		0x0080
#define BUSST_RDY4TXNOW		0x0100

#define CRYSTAL_SEMI_EISA_ID	0x630E

/* Origin: CS8900A datasheet */
#define CS8X0_PID_CS8900A_B	0x0700
#define CS8X0_PID_CS8900A_C	0x0800
#define CS8X0_PID_CS8900A_D	0x0900
#define CS8X0_PID_CS8900A_F	0x0A00
/* Origin: CS8920A datasheet */
#define CS8X0_PID_CS8920_B 	0x6100
#define CS8X0_PID_CS8920_C 	0x6200
#define CS8X0_PID_CS8920_D 	0x6300
#define CS8X0_PID_CS8920A_AB 	0x6400
#define CS8X0_PID_CS8920A_C	0x6500

typedef enum {
	CS8900,
	CS8900A,
	CS8920,
	CS8920A,
} cs89x0_chip_type_t;

struct cs89x0_chip {
	cs89x0_chip_type_t chip_type;
	const char *name;
};

struct cs89x0_product {
	u16 product_id;
	cs89x0_chip_type_t chip_type;
	const char *rev_name;
};

static struct cs89x0_chip cs89x0_chips[] = {
	{CS8900A, "CS8900A"},
	{CS8920A, "CS8920A"},	/* EOL: March 30, 2003 */
	{CS8900, "CS8900"},	/* EOL: September 31, 1999 */
	{CS8920, "CS8920"},	/* EOL: December 9, 1998 */
};

static struct cs89x0_product cs89x0_product_ids[] = {
	{CS8X0_PID_CS8900A_F, CS8900A, "F"},
	{CS8X0_PID_CS8900A_D, CS8900A, "D"},
	{CS8X0_PID_CS8900A_C, CS8900A, "C"},
	{CS8X0_PID_CS8900A_B, CS8900A, "B"},
	{CS8X0_PID_CS8920A_C, CS8920A, "C"},
	{CS8X0_PID_CS8920A_AB, CS8920A, "A/B"},
	{CS8X0_PID_CS8920_D, CS8920, "D"},
	{CS8X0_PID_CS8920_C, CS8920, "C"},
	{CS8X0_PID_CS8920_B, CS8920, "B"},
};

struct cs8900_priv {
	void *regs;
	struct cs89x0_product *product;
	struct cs89x0_chip *chip;
};

/* Read a 16-bit value from PacketPage Memory using I/O Space operation */
static u16 cs8900_ior(struct cs8900_priv *priv, int reg)
{
	u16 result;
	writew(reg, priv->regs + CS8900_PP_PTR);
	result = readw(priv->regs + CS8900_PP_DATA0);
	return result;
}

/* Write a 16-bit value to PacketPage Memory using I/O Space operation */
static void cs8900_iow(struct cs8900_priv *priv, int reg, u16 value)
{
	writew(reg, priv->regs + CS8900_PP_PTR);
	writew(value, priv->regs + CS8900_PP_DATA0);
}

static void cs8900_reginit(struct cs8900_priv *priv)
{
	/* receive only error free packets addressed to this card */
	cs8900_iow(priv, PP_REG_RXCTL,
		   RXCTL_BROADCASTA | RXCTL_INDIVIDUALA | RXCTL_RXOKA);
	/* do not generate any interrupts on receive operations */
	cs8900_iow(priv, PP_REG_RXCFG, 0);
	/* do not generate any interrupts on transmit operations */
	cs8900_iow(priv, PP_REG_TXCFG, 0);
	/* do not generate any interrupts on buffer operations */
	cs8900_iow(priv, PP_REG_BUFCFG, 0);
	/* enable transmitter/receiver mode */
	cs8900_iow(priv, PP_REG_LINECTL,
		   LINECTL_SERRXON | LINECTL_SERTXON);
}

static void cs8900_reset(struct cs8900_priv *priv)
{
	u16 v;
	uint64_t tmo;

	v = cs8900_ior(priv, PP_REG_SELFCTL);
	v |= SELFCTL_RESET;
	cs8900_iow(priv, PP_REG_SELFCTL, v);

	/* wait 200ms */
	udelay(200000);

	tmo = get_time_ns();
	while ((cs8900_ior(priv, PP_REG_SELFST) & SELFST_INITD) == 0) {
		if (is_timeout(tmo, 1 * SECOND)) {
			printf("%s: timeout\n", __func__);
			break;
		}
	}
}


static int cs8900_dev_init(struct eth_device *dev)
{
	return 0;
}

static int cs8900_open(struct eth_device *dev)
{
	struct cs8900_priv *priv = (struct cs8900_priv *)dev->priv;
	cs8900_reset(priv);
	cs8900_reginit(priv);
	return 0;
}

static int cs8900_send(struct eth_device *dev, void *eth_data,
		       int data_length)
{
	struct cs8900_priv *priv = (struct cs8900_priv *)dev->priv;
	u16 *addr;
	u16 v;

	writew(TXCMD_TXSTART_FULL, priv->regs + CS8900_TXCMD);
	writew(data_length, priv->regs + CS8900_TXLEN);

	v = cs8900_ior(priv, PP_REG_BUSST);

	if (v & BUSST_TXBIDERR) {
		printf("%s: frame error\n", __func__);
		return -1;
	}
	if ((v & BUSST_RDY4TXNOW) == 0) {
		printf("%s: busy\n", __func__);
		return -1;
	}

	for (addr = eth_data; data_length > 0; data_length -= 2) {
		writew(*addr++, priv->regs + CS8900_RTDATA0);
	}

	return 0;
}

static int cs8900_recv(struct eth_device *dev)
{
	struct cs8900_priv *priv = (struct cs8900_priv *)dev->priv;
	int len = 0;
	u16 status;
	u16 *addr;
	int i;

	status = cs8900_ior(priv, PP_REG_RXEVENT);
	if ((status & RXEVENT_RXOK) == 0) {
		/* No packet received. */
		return 0;
	}

	status = readw(priv->regs + CS8900_RTDATA0);
	len = readw(priv->regs + CS8900_RTDATA0);

	for (addr = (u16 *) NetRxPackets[0], i = len >> 1; i > 0; i--) {
		*addr++ = readw(priv->regs + CS8900_RTDATA0);
	}
	if (len & 1) {
		*addr++ = readw(priv->regs + CS8900_RTDATA0);
	}
	net_receive(NetRxPackets[0], len);

	return len;
}

static void cs8900_halt(struct eth_device *dev)
{
	struct cs8900_priv *priv = (struct cs8900_priv *)dev->priv;
	cs8900_iow(priv, PP_REG_BUSCTL, 0);
	cs8900_iow(priv, PP_REG_TESTCTL, 0);
	cs8900_iow(priv, PP_REG_SELFCTL, 0);
	cs8900_iow(priv, PP_REG_LINECTL, 0);
	cs8900_iow(priv, PP_REG_BUFCFG, 0);
	cs8900_iow(priv, PP_REG_TXCFG, 0);
	cs8900_iow(priv, PP_REG_RXCTL, 0);
	cs8900_iow(priv, PP_REG_RXCFG, 0);
}

static int cs8900_get_ethaddr(struct eth_device *dev, unsigned char *mac)
{
	struct cs8900_priv *priv = (struct cs8900_priv *)dev->priv;
	int i;
	for (i = 0; i < 6 / 2; i++) {
		u16 v;
		v = cs8900_ior(priv, PP_AF_IA + i * 2);
		mac[i * 2] = v & 0xFF;
		mac[i * 2 + 1] = v >> 8;
	}

	return 0;
}

static int cs8900_set_ethaddr(struct eth_device *dev, unsigned char *mac)
{
	struct cs8900_priv *priv = (struct cs8900_priv *)dev->priv;
	int i;
	for (i = 0; i < 6 / 2; i++) {
		u16 v;
		v = (mac[i * 2 + 1] << 8) | mac[i * 2];
		cs8900_iow(priv, PP_AF_IA + i * 2, v);
	}
	return 0;
}

static const char *yesno_str(int v)
{
	return v ? "yes" : "no";
}

static void cs8900_info(struct device_d *dev)
{
	struct eth_device *edev = (struct eth_device *)dev->type_data;
	struct cs8900_priv *priv = (struct cs8900_priv *)edev->priv;
	u16 v;

	printf("%s Rev. %s (PID: 0x%04x) at 0x%p\n", priv->chip->name,
	       priv->product->rev_name, priv->product->product_id,
	       priv->regs);

	v = cs8900_ior(priv, PP_REG_LINEST);
	printf("Register 14: LineST     0x%04x\n", v);
	printf("  Link OK: %s\n", yesno_str(v & LINEST_LINKOK));
	printf("  Using AUI: %s\n", yesno_str(v & LINEST_AUI));
	printf("  Using 10Base-T: %s\n", yesno_str(v & LINEST_10BT));
	printf("  Polarity OK: %s\n", yesno_str(v & LINEST_POLARITYOK));
	printf("  Incoming frame: %s\n", yesno_str(v & LINEST_CRS));

	v = cs8900_ior(priv, PP_REG_SELFST);
	printf("Register 16: SelfST     0x%04x\n", v);
	printf("  Voltage: %sV\n", (v & SELFST_33VACTIVE) ? "3.3" : "5");
	printf("  Initialization complete: %s\n",
	       yesno_str(v & SELFST_INITD));
	printf("  EEPROM busy: %s\n", yesno_str(v & SELFST_SIBUSY));
	printf("  EEPROM present: %s\n", yesno_str(v & SELFST_EEPROMP));
	printf("  EEPROM checksum OK: %s\n",
	       yesno_str(v & SELFST_EEPROMOK));
	printf("  EL present: %s\n", yesno_str(v & SELFST_ELPRESENT));
	printf("  EEPROM size: %s words\n",
	       (v & SELFST_EESIZE) ? "64" : "128 or 256");
}

static int cs8900_check_id(struct cs8900_priv *priv)
{
	int result = -1;
	struct cs89x0_product *product = NULL;
	struct cs89x0_chip *chip = NULL;
	int i;
	u16 v;
	v = cs8900_ior(priv, PP_BI_VID);
	if (v != CRYSTAL_SEMI_EISA_ID) {
		printf("No CS89x0 variant found at 0x%p vid: 0x%04x\n",
		       priv->regs, v);
		return -1;
	}
	v = cs8900_ior(priv, PP_BI_PID);
	for (i = 0; i < ARRAY_SIZE(cs89x0_product_ids); i++) {
		if (cs89x0_product_ids[i].product_id == v) {
			product = &(cs89x0_product_ids[i]);
			break;
		}
	}
	if (product == NULL) {
		printf("Unable to identify CS89x0 variant 0x%04x\n", v);
		goto out;
	}
	for (i = 0; i < ARRAY_SIZE(cs89x0_chips); i++) {
		if (cs89x0_chips[i].chip_type == product->chip_type) {
			chip = &(cs89x0_chips[i]);
			break;
		}
	}
	if (chip == NULL) {
		printf("Invalid chip type %d\n", product->chip_type);
		goto out;
	}
	printf("%s Rev. %s (PID: 0x%04x) found at 0x%p\n", chip->name,
	       product->rev_name, v, priv->regs);
	priv->chip = chip;
	priv->product = product;
	result = 0;
      out:
	return result;
}

static int cs8900_probe(struct device_d *dev)
{
	struct eth_device *edev;
	struct cs8900_priv *priv;

	debug("cs8900_init()\n");

	priv = (struct cs8900_priv *)xmalloc(sizeof(*priv));
	priv->regs = dev_request_mem_region(dev, 0);
	if (cs8900_check_id(priv)) {
		free(priv);
		return -1;
	}

	edev = (struct eth_device *)xmalloc(sizeof(struct eth_device));
	dev->type_data = edev;
	edev->priv = priv;

	edev->init = cs8900_dev_init;
	edev->open = cs8900_open;
	edev->send = cs8900_send;
	edev->recv = cs8900_recv;
	edev->halt = cs8900_halt;
	edev->get_ethaddr = cs8900_get_ethaddr;
	edev->set_ethaddr = cs8900_set_ethaddr;
	edev->parent = dev;

	eth_register(edev);
	return 0;
}

static struct driver_d cs8900_driver = {
	.name = "cs8900",
	.probe = cs8900_probe,
	.info = cs8900_info,
};

static int cs8900_init(void)
{
	register_driver(&cs8900_driver);
	return 0;
}

device_initcall(cs8900_init);
