blob: de6d038674a29a6e20d7bd9cc12c11b70e19e2ee [file] [log] [blame]
/*
* 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 <asm/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 = (u16 *)dev->map_base;
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;
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);